local _, T = ...
local XU, int, ScrollableBlockData, positionArchive, hoverWatcher =
	T.exUI, {}, {}, setmetatable({}, { __mode = "k" }), nil
local MIN_SCROLL_ENTRIES, MAX_VISIBLE_ENTRIES, WHEEL_STEP = 20, 16, 8
local assert, getWidgetData, _, setWidgetData = XU:GetImpl()
DropDownList1:HookScript("OnHide", function()
	wipe(positionArchive)
end)

local getFreeBlock
do
	local pool = {}
	local function releaseBlock(block)
		local d = getWidgetData(block, ScrollableBlockData)
		if d then
			block:Hide()
			block:SetParent(nil)
			block:ClearAllPoints()
			pool[block], d.dataList, d.entryFormat, d.entrySelect = 1, nil
		end
	end
	local function newBlock()
		local block = CreateFrame("Frame")
		block:Hide()
		block:SetScript("OnHide", releaseBlock)
		block:SetScript("OnMouseWheel", int.OnMouseWheel)
		block:SetHitRectInsets(-4, -24, -8, -8)
		local bb = block:CreateTexture(nil, "BACKGROUND")
		bb:SetColorTexture(0.3, 0.3, 0.3)
		bb:SetPoint("BOTTOMLEFT", -4, -0.5)
		bb:SetPoint("BOTTOMRIGHT", 0, -0.5)
		local clipRoot = CreateFrame("Frame", nil, block)
		clipRoot:SetAllPoints()
		clipRoot:SetClipsChildren(true)
		local scrollOrigin = CreateFrame("Frame", nil, clipRoot)
		scrollOrigin:SetPoint("TOPLEFT")
		scrollOrigin:SetPoint("TOPRIGHT")
		scrollOrigin:SetHeight(1)
		local scrollBar = XU:Create("ScrollBar", nil, block)
		scrollBar:SetPoint("TOPRIGHT", -1, 0)
		scrollBar:SetPoint("BOTTOMRIGHT", -1, 0)
		scrollBar:SetScript("OnValueChanged", int.OnScrollChanged)
		scrollBar:SetWindowRange(MAX_VISIBLE_ENTRIES)
		scrollBar:SetStepsPerPage(MAX_VISIBLE_ENTRIES - 2)
		local buttons = {}
		for i = 1, MAX_VISIBLE_ENTRIES + 1 do
			local b = CreateFrame("CheckButton", nil, clipRoot, nil, i)
			b:SetSize(100, 16)
			b:SetPoint("TOPLEFT", scrollOrigin, 0, 16 - 16 * i)
			b:SetPoint("TOPRIGHT", scrollOrigin, -16, 16 - 16 * i)
			b:SetScript("OnClick", int.OnEntryClick)
			b:SetHighlightTexture([[Interface\QuestFrame\UI-QuestTitleHighlight]])
			b:SetCheckedTexture([[Interface\Common\UI-DropDownRadioChecks]])
			b:SetNormalTexture([[Interface\Common\UI-DropDownRadioChecks]])
			b:SetNormalFontObject(GameFontHighlightSmallLeft)
			b:SetDisabledFontObject(GameFontHighlightSmallLeft)
			b:SetText("The Fifth Surprise")
			b:GetFontString():ClearAllPoints()
			b:GetFontString():SetPoint("LEFT", 22, 0)
			local h, c, n = b:GetHighlightTexture(), b:GetCheckedTexture(), b:GetNormalTexture()
			h:SetBlendMode("ADD")
			h:SetAllPoints()
			c:SetTexCoord(0, 0.5, 0.5, 1)
			c:ClearAllPoints()
			c:SetSize(16, 16)
			c:SetPoint("LEFT", 3, 0)
			n:SetTexCoord(0.5, 1, 0.5, 1)
			n:ClearAllPoints()
			n:SetSize(16, 16)
			n:SetPoint("LEFT", 3, 0)
			buttons[i] = b
		end
		local d = {
			root = block,
			clip = clipRoot,
			origin = scrollOrigin,
			scrollBar = scrollBar,
			buttons = buttons,
			bottomTex = bb,
		}
		setWidgetData(block, ScrollableBlockData, d)
		return block
	end
	function getFreeBlock()
		local r = next(pool) or newBlock()
		pool[r] = nil
		return r, getWidgetData(r, ScrollableBlockData)
	end
end

function int:OnEntryClick()
	local d = getWidgetData(self:GetParent():GetParent(), ScrollableBlockData)
	local entrySelect, arg1 = d.entrySelect, d.dataList[self:GetID()]
	PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
	CloseDropDownMenus()
	entrySelect(nil, arg1)
end
function int:OnMouseWheel(delta)
	local d = getWidgetData(self, ScrollableBlockData)
	d.scrollBar:Step(-delta * WHEEL_STEP, true)
	int.SyncToBar(d, false)
end
function int:OnScrollChanged(_, internalEvent)
	if internalEvent then
		int.SyncToBar(getWidgetData(self:GetParent(), ScrollableBlockData), false)
	end
end
function int.SyncToBar(d, fullSync)
	local buttons, scrollBar, dataList, entryFormat = d.buttons, d.scrollBar, d.dataList, d.entryFormat
	local position, isDone = scrollBar:GetValue(), scrollBar:IsValueAtRest()
	positionArchive[dataList] = position
	local oy, baseOffset = (position % 1) * 16, math.floor(position)
	d.origin:SetPoint("TOPLEFT", 0, oy)
	d.origin:SetPoint("TOPRIGHT", 0, oy)
	for i = 1, #buttons do
		local w, eid = buttons[i], i + baseOffset
		local ek = dataList[eid]
		w:SetShown(ek ~= nil)
		if ek ~= nil then
			if fullSync or w:GetID() ~= eid then
				local text, selected = entryFormat(ek, dataList)
				w:SetText(text)
				w:SetChecked(selected)
				w:SetID(eid)
				w:GetFontString():GetLeft() -- BUG[8.1.5] Without this, it sometimes gets lost.
			end
			w:SetEnabled(isDone)
		end
	end
end
if UIDropDownMenu_StopCounting then
	hoverWatcher = CreateFrame("Frame", nil, DropDownList1)
	hoverWatcher:Hide()
	hoverWatcher:SetScript("OnUpdate", function(self)
		for l = (DropDownList1.isCounting or self.suspended) and UIDROPDOWNMENU_MENU_LEVEL or 0, 1, -1 do
			local m = _G["DropDownList" .. l]
			if m and m:IsMouseOver() then
				self.suspended = true
				return DropDownList1.isCounting and UIDropDownMenu_StopCounting(DropDownList1)
			end
		end
		self.suspended = self.suspended and UIDropDownMenu_StartCounting(DropDownList1) and nil
	end)
	hoverWatcher:SetScript("OnHide", function(self)
		self.suspended = nil
		self:Hide()
	end)
end

local function DisplayScrollableDropDownEntryList(level, dataList, entryFormatter, entrySelect, willContinue)
	assert(type(level) == "number", "ScrollableDropDownList: level argument must be a number")
	assert(type(dataList) == "table", "ScrollableDropDownList: dataList argument must be a table")
	assert(type(entryFormatter) == "function", "ScrollableDropDownList: entryFormatter argument must be a function")
	assert(type(entrySelect) == "function", "ScrollableDropDownList: entrySelect argument must be a function")
	if #dataList < MIN_SCROLL_ENTRIES then
		local info = { func = entrySelect, minWidth = level == 1 and UIDROPDOWNMENU_OPEN_MENU:GetWidth() - 40 or nil }
		for i = 1, #dataList do
			local k = dataList[i]
			info.arg1, info.text, info.checked = k, entryFormatter(k, dataList)
			UIDropDownMenu_AddButton(info, level)
		end
		return
	end
	local ddlName, maxV, arch = "DropDownList" .. level, #dataList - MAX_VISIBLE_ENTRIES, positionArchive[dataList]
	local host, block, d = _G[ddlName], getFreeBlock()
	block.parent, d.dataList, d.entryFormat, d.entrySelect = host, dataList, entryFormatter, entrySelect
	block:SetParent(host)
	local n1, nX = host.numButtons + 1, MAX_VISIBLE_ENTRIES + host.numButtons
	local minWidth, testFS =
		math.max(120, level == 1 and UIDROPDOWNMENU_OPEN_MENU:GetWidth() - 40 or 0), d.buttons[1]:GetFontString()
	for i = 1, #dataList do
		local text = entryFormatter(dataList[i], dataList)
		testFS:SetText(text)
		minWidth = math.max(minWidth, 60 + testFS:GetStringWidth())
	end
	local info = { notClickable = true, notCheckable = true, minWidth = minWidth }
	for i = n1, nX do
		UIDropDownMenu_AddButton(info, level)
	end
	local b1, bX = _G[ddlName .. "Button" .. n1], _G[ddlName .. "Button" .. nX]
	block:SetPoint("TOPLEFT", b1, 0, n1 == 1 and 5 or 0)
	d.clip:SetPoint("TOPLEFT", 0, n1 == 1 and -5 or 0)
	block:SetPoint("BOTTOMRIGHT", bX, "BOTTOMRIGHT", 0, willContinue and 0 or -4)
	d.scrollBar:SetMinMaxValues(0, maxV)
	d.scrollBar:SetValue(arch and arch <= maxV and arch or 0)
	int.SyncToBar(d, true)
	d.bottomTex:SetHeight(PixelUtil.GetPixelToUIUnitFactor() / block:GetEffectiveScale() * 1.001)
	d.bottomTex:SetShown(not not willContinue)
	block:Show()
	block:SetFrameLevel(b1:GetFrameLevel() + 2)
	d.scrollBar:SetFrameLevel(block:GetFrameLevel() + 3)
	if hoverWatcher then
		hoverWatcher:Show()
	end
end

XU:RegisterFactory("ScrollableDropDownList", DisplayScrollableDropDownEntryList)
