local addonName, SlickUI = ...

local ObjectiveTrackerScroll = {}
SlickUI.ObjectiveTrackerScroll = ObjectiveTrackerScroll

local WHEEL_STEP = 48

local function Clamp(value, minimum, maximum)
	return math.max(minimum, math.min(maximum, value))
end

local function FindScrollFrame(frame, bestFrame, bestRange)
	if not frame or not frame.GetChildren then return bestFrame, bestRange or 0 end
	bestRange = bestRange or 0
	for index = 1, select("#", frame:GetChildren()) do
		local child = select(index, frame:GetChildren())
		if child and child.GetObjectType and child:GetObjectType() == "ScrollFrame"
			and child.GetVerticalScrollRange and child.SetVerticalScroll then
			local ok, range = pcall(child.GetVerticalScrollRange, child)
			range = ok and tonumber(range) or 0
			if not bestFrame or range > bestRange then
				bestFrame, bestRange = child, range
			end
		end
		bestFrame, bestRange = FindScrollFrame(child, bestFrame, bestRange)
	end
	return bestFrame, bestRange
end

local function ResolveScrollFrame(tracker)
	local best, bestRange = FindScrollFrame(tracker)
	for _, candidate in ipairs({ tracker.ScrollFrame, tracker.ScrollContainer, tracker.ScrollBox }) do
		if candidate and candidate.GetVerticalScrollRange and candidate.SetVerticalScroll then
			local ok, range = pcall(candidate.GetVerticalScrollRange, candidate)
			range = ok and tonumber(range) or 0
			if not best or range > bestRange then best, bestRange = candidate, range end
		end
	end
	return best, bestRange or 0
end

local function HookWheelTree(frame, handler)
	if not frame or not frame.GetChildren then return end
	if frame.EnableMouseWheel and not frame.SlickUIObjectiveWheelHook then
		frame:EnableMouseWheel(true)
		frame:HookScript("OnMouseWheel", handler)
		frame.SlickUIObjectiveWheelHook = true
	end
	for index = 1, select("#", frame:GetChildren()) do
		HookWheelTree(select(index, frame:GetChildren()), handler)
	end
end

function ObjectiveTrackerScroll:ScrollBy(direction)
	local tracker = _G.ObjectiveTrackerFrame
	local target = tracker and ResolveScrollFrame(tracker) or self.ScrollFrame
	if target then self.ScrollFrame = target end
	if not target then self:Attach(); target = self.ScrollFrame end
	if not target then return end
	local range = math.max(0, target:GetVerticalScrollRange() or 0)
	local current = target:GetVerticalScroll() or 0
	local nextOffset = Clamp(current + (direction * WHEEL_STEP), 0, range)
	target:SetVerticalScroll(nextOffset)
	if self.Controls then
		self.Controls.Up:SetEnabled(nextOffset > 0)
		self.Controls.Down:SetEnabled(nextOffset < range)
	end
end

function ObjectiveTrackerScroll:CreateControls(tracker)
	if self.Controls then return self.Controls end
	-- Parent to UIParent so tracker clipping and child-frame strata cannot hide
	-- the controls, while anchoring them to the tracker so they still follow it.
	local controls = CreateFrame("Frame", "SlickUIObjectiveTrackerScrollControls", UIParent, "BackdropTemplate")
	controls:SetSize(52, 24)
	controls:SetPoint("BOTTOMRIGHT", tracker, "BOTTOMRIGHT", -4, -26)
	controls:SetFrameStrata("DIALOG")
	controls:SetFrameLevel(100)
	controls:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1 })
	controls:SetBackdropColor(0.02, 0.02, 0.02, 0.88)
	controls:SetBackdropBorderColor(1, 0.58, 0, 1)

	local up = CreateFrame("Button", nil, controls, "UIPanelButtonTemplate")
	up:SetSize(24, 20); up:SetPoint("LEFT", 2, 0); up:SetText("^")
	up:SetScript("OnClick", function() ObjectiveTrackerScroll:ScrollBy(-1) end)
	local down = CreateFrame("Button", nil, controls, "UIPanelButtonTemplate")
	down:SetSize(24, 20); down:SetPoint("LEFT", up, "RIGHT", 0, 0); down:SetText("v")
	down:SetScript("OnClick", function() ObjectiveTrackerScroll:ScrollBy(1) end)
	controls.Up, controls.Down = up, down
	tracker:HookScript("OnShow", function() controls:Show() end)
	tracker:HookScript("OnHide", function() controls:Hide() end)
	controls:SetShown(tracker:IsShown())
	self.Controls = controls
	return controls
end

function ObjectiveTrackerScroll:Attach()
	local tracker = _G.ObjectiveTrackerFrame
	if not tracker then return end
	self:CreateControls(tracker)

	local scrollFrame = ResolveScrollFrame(tracker)
	if scrollFrame then self.ScrollFrame = scrollFrame end
	if not self.WheelHandler then
		self.WheelHandler = function(_, delta)
			local target = ObjectiveTrackerScroll.ScrollFrame
			if not target then return end
			local range = math.max(0, target:GetVerticalScrollRange() or 0)
			if range == 0 then return end
			local current = target:GetVerticalScroll() or 0
			target:SetVerticalScroll(Clamp(current - (delta * WHEEL_STEP), 0, range))
		end
	end

	if scrollFrame then HookWheelTree(tracker, self.WheelHandler) end
	tracker.SlickUIMouseWheelScroll = true

	if not tracker.SlickUIObjectiveChildRefresh then
		tracker:HookScript("OnShow", function()
			C_Timer.After(0, function() ObjectiveTrackerScroll:Attach() end)
		end)
		tracker.SlickUIObjectiveChildRefresh = true
	end

	if self.RefreshTicker then self.RefreshTicker:Cancel() end
	local passes = 0
	self.RefreshTicker = C_Timer.NewTicker(1, function(ticker)
		passes = passes + 1
		local discovered = ResolveScrollFrame(tracker)
		if discovered then
			ObjectiveTrackerScroll.ScrollFrame = discovered
			HookWheelTree(tracker, ObjectiveTrackerScroll.WheelHandler)
		end
		if passes >= 10 then ticker:Cancel(); ObjectiveTrackerScroll.RefreshTicker = nil end
	end)
end

local events = CreateFrame("Frame")
events:RegisterEvent("PLAYER_LOGIN")
events:RegisterEvent("ADDON_LOADED")
events:SetScript("OnEvent", function(_, event, loadedAddon)
	if event == "PLAYER_LOGIN" or loadedAddon == "Blizzard_ObjectiveTracker" then
		C_Timer.After(0, function() ObjectiveTrackerScroll:Attach() end)
	end
end)
