local PT = AhUI
if not PT then return end
local L = PT.L

PT.DisEnchantSuper = PT.DisEnchantSuper or {}
local DES = PT.DisEnchantSuper
_G.AhUIDisEnchantSuper = DES

local FONT = PT.FONT or STANDARD_TEXT_FONT
local DISENCHANT_SPELL_ID = 13262
local FRAME_NAME = "AhUIDESFrame"
local SECURE_BUTTON_NAME = "AhUIDES_DisenchantButton"
local ROW_H = 28
local VISIBLE_ROWS = 9
local WINDOW_W = 540
local WINDOW_H = 436

local WEAPON_CLASS = 2
local ARMOR_CLASS = 4
local QUALITY_UNCOMMON = 2
local QUALITY_RARE = 3
local QUALITY_EPIC = 4

local function T(key, fallback)
	return (L and L[key]) or fallback
end

local nonDisenchantable = {
	-- Classic era
	[38] = true,
	[45] = true,
	[49] = true,
	[53] = true,
	[127] = true,
	[148] = true,
	[154] = true,
	[2105] = true,
	[2575] = true,
	[2576] = true,
	[2577] = true,
	[2579] = true,
	[2587] = true,
	[3426] = true,
	[3427] = true,
	[3428] = true,
	[4330] = true,
	[4332] = true,
	[4333] = true,
	[4334] = true,
	[4335] = true,
	[4336] = true,
	[4344] = true,
	[5107] = true,
	[6096] = true,
	[6097] = true,
	[6117] = true,
	[6120] = true,
	[6125] = true,
	[6134] = true,
	[6136] = true,
	[6384] = true,
	[6385] = true,
	[6795] = true,
	[6796] = true,
	[6833] = true,
	[10034] = true,
	[10052] = true,
	[10054] = true,
	[10055] = true,
	[10056] = true,
	[11287] = true,
	[11288] = true,
	[11289] = true,
	[11290] = true,
	[13347] = true,
	[16059] = true,
	[16060] = true,
	[17723] = true,
	[18231] = true,
	[20406] = true,
	[20407] = true,
	[20408] = true,
	[20897] = true,
	[20901] = true,
	[21766] = true,
	[23345] = true,
	[23473] = true,
	[23476] = true,
	[24143] = true,
	[31279] = true,
	-- Wrath era
	[41248] = true,
	[41249] = true,
	[41250] = true,
	[41251] = true,
	[41252] = true,
	[41253] = true,
	[41254] = true,
	[41255] = true,
	[42360] = true,
	[42361] = true,
	[42363] = true,
	[42365] = true,
	[42368] = true,
	[42369] = true,
	[42370] = true,
	[42371] = true,
	[42372] = true,
	[42373] = true,
	[42374] = true,
	[42375] = true,
	[42376] = true,
	[42377] = true,
	[42378] = true,
	[44693] = true,
	[44694] = true,
	[45664] = true,
	[45666] = true,
	[45667] = true,
	[45668] = true,
	[45669] = true,
	[45670] = true,
	[45671] = true,
	[45672] = true,
	[45673] = true,
	[45674] = true,
	[48663] = true,
	[49567] = true,
	-- Cata era
	[52252] = true,
	[52485] = true,
	[52486] = true,
	[52487] = true,
	[52488] = true,
	[52548] = true,
	[53852] = true,
	[60223] = true,
	-- MoP/WoD/Legion era
	[68611] = true,
	[75274] = true,
	[84661] = true,
	[89586] = true,
	[97826] = true,
	[97827] = true,
	[97828] = true,
	[97829] = true,
	[97830] = true,
	[97831] = true,
	[97832] = true,
	[109262] = true,
	[122604] = true,
	[127842] = true,
	[128023] = true,
	[128024] = true,
	[141408] = true,
	-- BFA era
	[151607] = true,
	[151771] = true,
	[151772] = true,
	[152632] = true,
	[152633] = true,
	[152635] = true,
	[152637] = true,
	[167081] = true,
	[167082] = true,
	[167177] = true,
	[167178] = true,
	[167179] = true,
	[167180] = true,
	[167181] = true,
	[167182] = true,
	[167183] = true,
	[167184] = true,
	[167185] = true,
	[167186] = true,
	[167187] = true,
	[167188] = true,
	[167189] = true,
	[167190] = true,
	[167191] = true,
	[167192] = true,
	[167193] = true,
	[167194] = true,
	[167195] = true,
	[167196] = true,
	[167197] = true,
	-- Shadowlands era
	[186056] = true,
	[186058] = true,
	[186163] = true,
	-- TWW/Midnight Pre-Launch gear
	[231955] = true,
	[231956] = true,
	[231957] = true,
	[231958] = true,
	[231959] = true,
	[231960] = true,
	[231961] = true,
	[231962] = true,
	[231963] = true,
	[231964] = true,
	[231965] = true,
	[231966] = true,
	[231967] = true,
	[231968] = true,
	[231969] = true,
	[231970] = true,
	[234830] = true,
	[234831] = true,
	[234832] = true,
	[234833] = true,
	[234834] = true,
	[234835] = true,
	[234836] = true,
	[234837] = true,
	[234881] = true,
	[234882] = true,
	[234883] = true,
	[234884] = true,
	[234885] = true,
	[234886] = true,
	[234887] = true,
	[234888] = true,
	[234924] = true,
	[234925] = true,
	[234926] = true,
	[234927] = true,
	[234928] = true,
	[234929] = true,
	[234930] = true,
	[234931] = true,
}

local function GetDB()
	AhUI_CharConfig = AhUI_CharConfig or {}
	AhUI_CharConfig.disenchantSuper = AhUI_CharConfig.disenchantSuper or {
		point = "CENTER",
		relPoint = "CENTER",
		x = 0,
		y = 0,
		binding = nil,
		excludedItems = {},
		filters = {
			[QUALITY_UNCOMMON] = true,
			[QUALITY_RARE] = true,
			[QUALITY_EPIC] = true,
		},
	}

	local db = AhUI_CharConfig.disenchantSuper
	db.filters = db.filters or {}
	db.excludedItems = db.excludedItems or {}
	if db.filters[QUALITY_UNCOMMON] == nil then db.filters[QUALITY_UNCOMMON] = true end
	if db.filters[QUALITY_RARE] == nil then db.filters[QUALITY_RARE] = true end
	if db.filters[QUALITY_EPIC] == nil then db.filters[QUALITY_EPIC] = true end
	return db
end

local function ApplyFont(fs, size, flags)
	if not fs then return end
	fs:SetFont(FONT, size or 12, flags or "")
end

local function CreateBackdrop(frame, edgeSize, bgAlpha, borderAlpha)
	frame:SetBackdrop({
		bgFile = "Interface\\Buttons\\WHITE8x8",
		edgeFile = "Interface\\Buttons\\WHITE8x8",
		edgeSize = edgeSize or 1,
	})
	frame:SetBackdropColor(0, 0, 0, bgAlpha or 0.95)
	frame:SetBackdropBorderColor(1, 1, 1, borderAlpha or 1)
end

local function CreateButtonBorder(button)
	local border = CreateFrame("Frame", nil, button, "BackdropTemplate")
	border:SetPoint("TOPLEFT", -1, 1)
	border:SetPoint("BOTTOMRIGHT", 1, -1)
	border:SetFrameLevel(math.max(0, button:GetFrameLevel() - 1))
	border:SetBackdrop({
		edgeFile = "Interface\\Buttons\\WHITE8x8",
		edgeSize = 1,
	})
	border:SetBackdropBorderColor(1, 1, 1, 0.15)
	button.border = border
end

local function CreateIconBorder(holder)
	local border = CreateFrame("Frame", nil, holder, "BackdropTemplate")
	border:SetFrameLevel(holder:GetFrameLevel() + 4)
	border:SetPoint("TOPLEFT", holder, "TOPLEFT", -2, 2)
	border:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", 2, -2)
	border:SetBackdrop({
		edgeFile = "Interface\\Buttons\\WHITE8x8",
		edgeSize = 2,
	})
	border:SetBackdropBorderColor(0, 0, 0, 1)
	holder.border = border
end

local function CreateTextButton(name, parent, width, height, label, isSecure)
	local template = isSecure and "SecureActionButtonTemplate,BackdropTemplate" or "BackdropTemplate"
	local b = CreateFrame("Button", name, parent, template)
	b:SetSize(width, height)
	CreateBackdrop(b, 1, 0.08, 0.18)

	b.bg = b:CreateTexture(nil, "BACKGROUND")
	b.bg:SetAllPoints()
	b.bg:SetColorTexture(1, 1, 1, 0.03)

	b.hl = b:CreateTexture(nil, "HIGHLIGHT")
	b.hl:SetAllPoints()
	b.hl:SetColorTexture(1, 1, 1, 0.08)

	b.text = b:CreateFontString(nil, "OVERLAY")
	ApplyFont(b.text, 12, "")
	b.text:SetPoint("CENTER")
	b.text:SetText(label or "")

	CreateButtonBorder(b)

	b:SetScript("OnMouseDown", function(self)
		self.text:SetPoint("CENTER", 1, -1)
	end)

	b:SetScript("OnMouseUp", function(self)
		self.text:SetPoint("CENTER", 0, 0)
	end)

	return b
end

local function GetQualityName(quality)
	if quality == QUALITY_UNCOMMON then
		return ITEM_QUALITY2_DESC or "Uncommon"
	elseif quality == QUALITY_RARE then
		return ITEM_QUALITY3_DESC or "Rare"
	elseif quality == QUALITY_EPIC then
		return ITEM_QUALITY4_DESC or "Epic"
	end
	return tostring(quality or "?")
end

local function GetQualityHex(quality)
	local c = ITEM_QUALITY_COLORS and ITEM_QUALITY_COLORS[quality]
	if c and c.hex then
		return c.hex
	end
	return "|cffffffff"
end

local function GetCurrentBindingText()
	local db = GetDB()
	if not db.binding or db.binding == "" then
		return T("DES_BINDING_NONE", "Not assigned")
	end
	return _G.GetBindingText(db.binding, "KEY_")
end

local function NormalizeBindingKey(key)
	if not key or key == "" or key == "UNKNOWN" then
		return nil
	end

	key = key:upper()

	if key == "LSHIFT" or key == "RSHIFT"
	or key == "LCTRL" or key == "RCTRL"
	or key == "LALT" or key == "RALT" then
		return nil
	end

	local parts = {}
	if IsControlKeyDown() then parts[#parts + 1] = "CTRL" end
	if IsAltKeyDown() then parts[#parts + 1] = "ALT" end
	if IsShiftKeyDown() then parts[#parts + 1] = "SHIFT" end
	parts[#parts + 1] = key
	return table.concat(parts, "-")
end

local function KnowsDisenchant()
	if C_SpellBook and C_SpellBook.IsSpellInSpellBook then
		return C_SpellBook.IsSpellInSpellBook(DISENCHANT_SPELL_ID)
	end
	if IsSpellKnownOrOverridesKnown then
		return IsSpellKnownOrOverridesKnown(DISENCHANT_SPELL_ID)
	end
	if IsPlayerSpell then
		return IsPlayerSpell(DISENCHANT_SPELL_ID)
	end
	return false
end

local function GetDisenchantSpellName()
	if C_Spell and C_Spell.GetSpellInfo then
		local info = C_Spell.GetSpellInfo(DISENCHANT_SPELL_ID)
		return info and info.name or nil
	end
	return GetSpellInfo(DISENCHANT_SPELL_ID)
end

local function IsStillAtBagSlot(item)
	if not item then return false end
	local info = C_Container.GetContainerItemInfo(item.bag, item.slot)
	return info and info.itemID == item.itemID
end

local function GetAllBags()
	local bags = {}
	for bag = 0, (NUM_BAG_SLOTS or 4) do
		bags[#bags + 1] = bag
	end
	if type(REAGENTBAG_CONTAINER) == "number" and REAGENTBAG_CONTAINER > (NUM_BAG_SLOTS or 4) then
		bags[#bags + 1] = REAGENTBAG_CONTAINER
	end
	return bags
end

local function IsProfessionEquipLoc(equipLoc)
	return equipLoc == "INVTYPE_PROFESSION_TOOL" or equipLoc == "INVTYPE_PROFESSION_GEAR"
end

function DES:IsDisenchantable(itemID, classID, quality, itemLevel, equipLoc)
	if not KnowsDisenchant() then
		return false
	end
	if not quality or quality < QUALITY_UNCOMMON or quality > QUALITY_EPIC then
		return false
	end
	if equipLoc == "INVTYPE_TABARD" or equipLoc == "INVTYPE_BODY" then
		return false
	end
	local isProfessionItem = IsProfessionEquipLoc(equipLoc)
	if not isProfessionItem and classID ~= WEAPON_CLASS and classID ~= ARMOR_CLASS then
		return false
	end
	if nonDisenchantable[itemID] then
		return false
	end
	return true
end

function DES:CreateItemInfo(bag, slot, containerInfo)
	local itemID = containerInfo.itemID
	local itemLink = C_Container.GetContainerItemLink(bag, slot)
	if not itemID or not itemLink then
		return nil
	end

	local _, _, _, equipLoc, iconInstant, classID, subClassID = GetItemInfoInstant(itemID)
	local itemQuality = containerInfo.quality or 0
	local icon = containerInfo.iconFileID or iconInstant

	local itemName
	if C_Item and C_Item.GetItemNameByID then
		itemName = C_Item.GetItemNameByID(itemID)
	end
	if not itemName then
		itemName = itemLink:match("%[(.-)%]") or ("item:" .. tostring(itemID))
	end

	local actualItemLevel = C_Item.GetDetailedItemLevelInfo and C_Item.GetDetailedItemLevelInfo(itemLink) or 0
	local itemLevel = actualItemLevel or 0

	if not self:IsDisenchantable(itemID, classID, itemQuality, itemLevel, equipLoc) then
		return nil
	end

	return {
		bag = bag,
		slot = slot,
		itemID = itemID,
		itemLink = itemLink,
		itemName = itemName,
		itemLevel = itemLevel,
		quality = itemQuality,
		equipLoc = equipLoc,
		classID = classID,
		subClassID = subClassID,
		count = containerInfo.stackCount or 1,
		icon = icon,
		isBound = containerInfo.isBound,
	}
end

function DES:PassesQualityFilter(item)
	local db = GetDB()
	return item
		and db.filters
		and db.filters[item.quality]
		and not self:IsExcluded(item.itemID)
end

function DES:GetSelectedKey(item)
	if not item then return nil end
	return tostring(item.bag) .. ":" .. tostring(item.slot)
end

function DES:IsExcluded(itemID)
	local db = GetDB()
	return itemID and db.excludedItems and db.excludedItems[itemID] == true
end

function DES:AddExcludedItem(itemID)
	if not itemID then
		return
	end

	local db = GetDB()
	db.excludedItems[itemID] = true

	if self.selectedItem and self.selectedItem.itemID == itemID then
		self.selectedItem = nil
	end

	self:RebuildFilteredItems()
end

function DES:ClearExcludedItems()
	local db = GetDB()
	wipe(db.excludedItems)

	self:RebuildFilteredItems()
end

function DES:QueueScanBags(delay)
	if self.scanQueued then
		return
	end

	self.scanQueued = true

	C_Timer.After(delay or 0.05, function()
		DES.scanQueued = false
		if DES.frame and DES.frame:IsShown() then
			DES:ScanBags()
		end
	end)
end

function DES:QueueOpenRefresh()
	if self.openRefreshQueued then
		return
	end

	self.openRefreshQueued = true

	local delays = { 0.05, 0.20, 0.45 }
	for i = 1, #delays do
		C_Timer.After(delays[i], function()
			if DES.frame and DES.frame:IsShown() then
				DES:ScanBags()
			end
			if i == #delays then
				DES.openRefreshQueued = false
			end
		end)
	end
end

function DES:ScanBags()
	self.items = self.items or {}
	wipe(self.items)

	local bags = GetAllBags()
	for i = 1, #bags do
		local bag = bags[i]
		local slots = C_Container.GetContainerNumSlots(bag)
		if slots and slots > 0 then
			for slot = 1, slots do
				local info = C_Container.GetContainerItemInfo(bag, slot)
				if info and info.itemID and not info.isLocked then
					local item = self:CreateItemInfo(bag, slot, info)
					if item then
						self.items[#self.items + 1] = item
					end
				end
			end
		end
	end

	table.sort(self.items, function(a, b)
		if a.quality ~= b.quality then
			return a.quality > b.quality
		end
		if a.itemLevel ~= b.itemLevel then
			return a.itemLevel < b.itemLevel
		end
		return (a.itemName or "") < (b.itemName or "")
	end)

	self:RebuildFilteredItems()
end

function DES:RebuildFilteredItems()
	self.filteredItems = self.filteredItems or {}
	wipe(self.filteredItems)

	local oldKey = self:GetSelectedKey(self.selectedItem)

	for i = 1, #self.items do
		local item = self.items[i]
		if self:PassesQualityFilter(item) then
			self.filteredItems[#self.filteredItems + 1] = item
		end
	end

	self.selectedItem = nil

	if oldKey then
		for i = 1, #self.filteredItems do
			if self:GetSelectedKey(self.filteredItems[i]) == oldKey then
				self.selectedItem = self.filteredItems[i]
				break
			end
		end
	end

	if not self.selectedItem then
		self.selectedItem = self.filteredItems[1]
	end

	self:UpdateSecureButton()

	if self.frame and self.frame:IsShown() then
		self:RefreshList()
		self:UpdateFooter()
	end
end

function DES:SetSelectedItem(item)
	self.selectedItem = item
	self:UpdateSecureButton()

	if self.frame and self.frame:IsShown() then
		self:RefreshList()
		self:UpdateFooter()
	end
end

function DES:UpdateSecureButton()
	if not self.frame or not self.frame.disenchantButton then
		return
	end

	local btn = self.frame.disenchantButton

	if InCombatLockdown() then
		self.pendingSecureRefresh = true
		return
	end

	self.pendingSecureRefresh = false

	btn:SetAttribute("type", nil)
	btn:SetAttribute("type1", nil)
	btn:SetAttribute("spell", nil)
	btn:SetAttribute("target-bag", nil)
	btn:SetAttribute("target-slot", nil)
	btn:SetAttribute("item", nil)
	btn:SetAttribute("target-item", nil)

	local valid =
		KnowsDisenchant()
		and self.selectedItem
		and IsStillAtBagSlot(self.selectedItem)

	local spellName = GetDisenchantSpellName()
	if not valid or not spellName or spellName == "" then
		if btn.text then
			btn.text:SetTextColor(0.45, 0.45, 0.45, 1)
		end
		if btn.bg then
			btn.bg:SetColorTexture(1, 1, 1, 0.015)
		end
		return
	end

	btn:SetAttribute("type", "spell")
	btn:SetAttribute("type1", "spell")
	btn:SetAttribute("spell", spellName)
	btn:SetAttribute("target-bag", self.selectedItem.bag)
	btn:SetAttribute("target-slot", self.selectedItem.slot)

	if btn.text then
		btn.text:SetTextColor(1, 1, 1, 1)
	end
	if btn.bg then
		btn.bg:SetColorTexture(1, 1, 1, 0.03)
	end
end

function DES:UpdateBindingText()
	if self.frame and self.frame.bindingValue then
		self.frame.bindingValue:SetText(GetCurrentBindingText())
	end
end

function DES:ApplyTemporaryBinding()
	local db = GetDB()

	if not self.frame then
		return false
	end

	if InCombatLockdown() then
		self.pendingBindingRefresh = true
		return false
	end

	ClearOverrideBindings(self.frame)

	if db.binding and db.binding ~= "" and self.frame:IsShown() then
		SetOverrideBindingClick(self.frame, false, db.binding, SECURE_BUTTON_NAME, "LeftButton")
	end

	self.pendingBindingRefresh = false
	return true
end

function DES:ClearTemporaryBinding()
	if not self.frame then
		return false
	end

	if InCombatLockdown() then
		self.pendingBindingRefresh = true
		return false
	end

	ClearOverrideBindings(self.frame)
	self.pendingBindingRefresh = false
	return true
end

function DES:ClearBinding()
	local db = GetDB()
	db.binding = nil

	if self.frame and self.frame:IsShown() then
		self:ClearTemporaryBinding()
	end

	self:UpdateBindingText()
	return true
end

function DES:SetBinding(binding)
	local db = GetDB()
	db.binding = binding and binding ~= "" and binding or nil

	self:UpdateBindingText()

	if self.frame and self.frame:IsShown() then
		self:ApplyTemporaryBinding()
	end

	return true
end

function DES:UpdateFooter()
	if not self.frame then return end

	self.frame.statusText:SetText((T("DES_STATUS_FORMAT", "Found: %d  •  After filter: %d")):format(#(self.items or {}), #(self.filteredItems or {})))

	if self.selectedItem then
		self.frame.selectedText:SetText(T("DES_SELECTED_PREFIX", "Selected: ") .. (self.selectedItem.itemLink or self.selectedItem.itemName or "-"))
	else
		self.frame.selectedText:SetText(T("DES_SELECTED_EMPTY", "Selected: —"))
	end

	if not KnowsDisenchant() then
		self.frame.noteText:SetText("|cffff6060" .. T("DES_NOTE_NO_SPELL", "Character does not know Disenchant.") .. "|r")
	elseif InCombatLockdown() then
		self.frame.noteText:SetText("|cffffcc66" .. T("DES_NOTE_IN_COMBAT", "In combat. The button will update after combat ends.") .. "|r")
	else
		self.frame.noteText:SetText(" ")
	end
end

function DES:RefreshList()
	if not self.frame then return end

	local total = #(self.filteredItems or {})
	local offset = FauxScrollFrame_GetOffset(self.frame.scrollFrame)
	FauxScrollFrame_Update(self.frame.scrollFrame, total, VISIBLE_ROWS, ROW_H)

	for i = 1, VISIBLE_ROWS do
		local row = self.frame.rows[i]
		local item = self.filteredItems[offset + i]

		if item then
			row.item = item
			row:Show()

			row.icon:SetTexture(item.icon or 134400)
			row.name:SetText((GetQualityHex(item.quality) or "|cffffffff") .. (item.itemName or "?") .. "|r")

			local bagText = (T("DES_BAG_SLOT_FORMAT", "Bag %d:%d")):format(item.bag, item.slot)
			local meta = (T("DES_ITEM_META_FORMAT", "ilvl %d  •  %s")):format(item.itemLevel or 0, bagText)
			if item.count and item.count > 1 then
				meta = meta .. ("  •  x%d"):format(item.count)
			end
			row.meta:SetText(meta)

			if self.selectedItem and self:GetSelectedKey(self.selectedItem) == self:GetSelectedKey(item) then
				row.sel:Show()
			else
				row.sel:Hide()
			end
		else
			row.item = nil
			row:Hide()
		end
	end
end

function DES:CreateBindCapture()
	if self.bindCapture then
		return
	end

	local f = CreateFrame("Frame", "AhUIDESBindCapture", UIParent, "BackdropTemplate")
	f:SetSize(360, 110)
	f:SetPoint("CENTER")
	f:SetFrameStrata("DIALOG")
	CreateBackdrop(f, 1, 0.95, 1)
	f:EnableKeyboard(true)
	f:EnableMouse(true)
	f:Hide()

	f.text = f:CreateFontString(nil, "OVERLAY")
	ApplyFont(f.text, 13, "")
	f.text:SetWidth(320)
	f.text:SetJustifyH("CENTER")
	f.text:SetPoint("CENTER")
	f.text:SetText(T("DES_BIND_CAPTURE", "Press a key for the disenchant button.\nEsc — cancel."))

	f:SetScript("OnShow", function(self)
		self:SetPropagateKeyboardInput(false)
	end)

	f:SetScript("OnKeyDown", function(self, key)
		if key == "ESCAPE" then
			self:Hide()
			return
		end

		local binding = NormalizeBindingKey(key)
		if not binding then
			return
		end

		DES:SetBinding(binding)
		self:Hide()
	end)

	f:SetScript("OnMouseDown", function(self)
		self:Hide()
	end)

	self.bindCapture = f
end

function DES:CreateRow(parent, index)
	local row = CreateFrame("Button", nil, parent)
	row:SetHeight(ROW_H)
	row:SetPoint("TOPLEFT", 6, -6 - ((index - 1) * ROW_H))
	row:SetPoint("TOPRIGHT", -26, -6 - ((index - 1) * ROW_H))
	row:RegisterForClicks("LeftButtonUp")

	row.bg = row:CreateTexture(nil, "BACKGROUND")
	row.bg:SetAllPoints()
	if index % 2 == 0 then
		row.bg:SetColorTexture(1, 1, 1, 0.04)
	else
		row.bg:SetColorTexture(1, 1, 1, 0.02)
	end

	row.hl = row:CreateTexture(nil, "HIGHLIGHT")
	row.hl:SetAllPoints()
	row.hl:SetColorTexture(1, 1, 1, 0.05)

	row.sel = row:CreateTexture(nil, "ARTWORK")
	row.sel:SetAllPoints()
	row.sel:SetColorTexture(0.40, 0.60, 1.00, 0.18)
	row.sel:Hide()

	row.excludeButton = CreateFrame("Button", nil, row, "BackdropTemplate")
	row.excludeButton:SetSize(16, 16)
	row.excludeButton:SetPoint("LEFT", 5, 0)
	CreateBackdrop(row.excludeButton, 1, 0.04, 0.12)

	row.excludeButton.bg = row.excludeButton:CreateTexture(nil, "BACKGROUND")
	row.excludeButton.bg:SetAllPoints()
	row.excludeButton.bg:SetColorTexture(1, 1, 1, 0.02)

	row.excludeButton.hl = row.excludeButton:CreateTexture(nil, "HIGHLIGHT")
	row.excludeButton.hl:SetAllPoints()
	row.excludeButton.hl:SetColorTexture(1, 1, 1, 0.04)

	row.excludeButton.icon = row.excludeButton:CreateTexture(nil, "ARTWORK")
	row.excludeButton.icon:SetPoint("CENTER")
	row.excludeButton.icon:SetSize(14, 14)
	row.excludeButton.icon:SetAtlas("common-icon-redx", true)
	row.excludeButton.icon:SetAlpha(0.7)

	CreateButtonBorder(row.excludeButton)

	row.excludeButton:RegisterForClicks("LeftButtonUp")
	row.excludeButton:SetScript("OnClick", function(self)
		local parentRow = self:GetParent()
		if parentRow and parentRow.item then
			DES:AddExcludedItem(parentRow.item.itemID)
		end
	end)

	row.excludeButton:SetScript("OnEnter", function(self)
		if self.icon then
			self.icon:SetAlpha(1)
		end
		GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
		GameTooltip:SetText(T("DES_EXCLUDE_TOOLTIP", "Exclude from list"), 1, 1, 1)
		GameTooltip:Show()
	end)

	row.excludeButton:SetScript("OnLeave", function(self)
		if self.icon then
			self.icon:SetAlpha(0.7)
		end
		GameTooltip:Hide()
	end)

	row.iconHolder = CreateFrame("Frame", nil, row, "BackdropTemplate")
	row.iconHolder:SetSize(20, 20)
	row.iconHolder:SetPoint("LEFT", row.excludeButton, "RIGHT", 6, 0)
	CreateIconBorder(row.iconHolder)

	row.icon = row.iconHolder:CreateTexture(nil, "ARTWORK")
	row.icon:SetAllPoints()
	row.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

	row.name = row:CreateFontString(nil, "OVERLAY")
	ApplyFont(row.name, 12, "")
	row.name:SetPoint("TOPLEFT", row.iconHolder, "TOPRIGHT", 8, 1)
	row.name:SetPoint("TOPRIGHT", -8, 1)
	row.name:SetJustifyH("LEFT")
	row.name:SetJustifyV("TOP")
	row.name:SetWordWrap(false)
	if row.name.SetMaxLines then
		row.name:SetMaxLines(1)
	end

	row.meta = row:CreateFontString(nil, "OVERLAY")
	ApplyFont(row.meta, 10, "")
	row.meta:SetPoint("BOTTOMLEFT", row.iconHolder, "BOTTOMRIGHT", 8, 0)
	row.meta:SetPoint("BOTTOMRIGHT", -8, 0)
	row.meta:SetJustifyH("LEFT")
	row.meta:SetJustifyV("BOTTOM")
	row.meta:SetWordWrap(false)
	if row.meta.SetMaxLines then
		row.meta:SetMaxLines(1)
	end
	row.meta:SetTextColor(0.72, 0.72, 0.76, 1)

	row:SetScript("OnClick", function(self)
		if MouseIsOver(self.excludeButton) then
			return
		end
		if self.item then
			DES:SetSelectedItem(self.item)
		end
	end)

	row:SetScript("OnEnter", function(self)
		if not self.item then return end
		GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
		GameTooltip:SetBagItem(self.item.bag, self.item.slot)
		GameTooltip:Show()
	end)

	row:SetScript("OnLeave", function()
		GameTooltip:Hide()
	end)

	return row
end

function DES:CreateUI()
	if self.frame then return end

	local db = GetDB()
	local f = CreateFrame("Frame", FRAME_NAME, UIParent, "BackdropTemplate")
	f:SetSize(WINDOW_W, WINDOW_H)
	f:SetPoint(db.point, UIParent, db.relPoint or db.point, db.x or 0, db.y or 0)
	CreateBackdrop(f, 1, 0.95, 1)
	f:SetMovable(true)
	f:EnableMouse(true)
	f:RegisterForDrag("LeftButton")
	f:SetScript("OnDragStart", f.StartMoving)
	f:SetScript("OnDragStop", function(self)
		self:StopMovingOrSizing()
		local point, _, relPoint, x, y = self:GetPoint()
		db.point = point
		db.relPoint = relPoint
		db.x = x
		db.y = y
	end)
	f:Hide()

	f:SetScript("OnShow", function()
		DES:ApplyTemporaryBinding()
		DES:UpdateBindingText()
		DES:QueueOpenRefresh()
	end)

	f:SetScript("OnHide", function()
		DES:ClearTemporaryBinding()
	end)

	table.insert(UISpecialFrames, FRAME_NAME)

	f.title = f:CreateFontString(nil, "OVERLAY")
	ApplyFont(f.title, 14, "OUTLINE")
	f.title:SetPoint("TOPLEFT", 14, -12)
	f.title:SetText(T("DES_TITLE", "Disenchant"))

	f.close = CreateFrame("Button", nil, f)
	f.close:SetSize(16, 16)
	f.close:SetPoint("TOPRIGHT", -10, -10)
	f.closeText = f.close:CreateFontString(nil, "OVERLAY")
	ApplyFont(f.closeText, 25, "")
	f.closeText:SetPoint("CENTER")
	f.closeText:SetText("×")
	f.closeText:SetTextColor(0.7, 0.7, 0.7)
	f.close:SetScript("OnEnter", function() f.closeText:SetTextColor(1, 0.4, 0.4) end)
	f.close:SetScript("OnLeave", function() f.closeText:SetTextColor(0.7, 0.7, 0.7) end)
	f.close:SetScript("OnClick", function() f:Hide() end)

	f.filterLabel = f:CreateFontString(nil, "OVERLAY")
	ApplyFont(f.filterLabel, 12, "")
	f.filterLabel:SetPoint("TOPLEFT", 14, -42)
	f.filterLabel:SetText(T("DES_FILTER_QUALITY", "Quality:"))

	local function CreateQualityCheck(parent, quality, anchor, xOff)
		local cb = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
		cb:SetPoint("LEFT", anchor, "RIGHT", xOff, 0)
		cb:SetChecked(db.filters[quality])
		cb.text = parent:CreateFontString(nil, "OVERLAY")
		ApplyFont(cb.text, 12, "")
		cb.text:SetPoint("LEFT", cb, "RIGHT", 2, 1)
		cb.text:SetText((GetQualityHex(quality) or "|cffffffff") .. GetQualityName(quality) .. "|r")
		cb:SetScript("OnClick", function(self)
			db.filters[quality] = self:GetChecked() and true or false
			DES:RebuildFilteredItems()
		end)
		return cb
	end

	f.q2 = CreateQualityCheck(f, QUALITY_UNCOMMON, f.filterLabel, 10)
	f.q3 = CreateQualityCheck(f, QUALITY_RARE, f.q2.text, 18)
	f.q4 = CreateQualityCheck(f, QUALITY_EPIC, f.q3.text, 18)

	f.refreshButton = CreateTextButton(nil, f, 90, 22, T("DES_REFRESH", "Refresh"), false)
	f.refreshButton:SetPoint("TOPRIGHT", -14, -38)
	f.refreshButton:SetScript("OnClick", function()
		DES:ScanBags()
	end)

	f.listBox = CreateFrame("Frame", nil, f, "BackdropTemplate")
	f.listBox:SetPoint("TOPLEFT", 14, -72)
	f.listBox:SetPoint("BOTTOMRIGHT", -14, 92)
	CreateBackdrop(f.listBox, 1, 0.04, 0.15)
	if f.listBox.SetClipsChildren then
		f.listBox:SetClipsChildren(true)
	end

	f.scrollFrame = CreateFrame("ScrollFrame", "AhUIDESScrollFrame", f.listBox, "FauxScrollFrameTemplate")
	f.scrollFrame:SetPoint("TOPLEFT", 6, -6)
	f.scrollFrame:SetPoint("BOTTOMRIGHT", -24, 6)
	f.scrollFrame:SetScript("OnVerticalScroll", function(self, offset)
		FauxScrollFrame_OnVerticalScroll(self, offset, ROW_H, function()
			DES:RefreshList()
		end)
	end)

	f.rows = {}
	for i = 1, VISIBLE_ROWS do
		f.rows[i] = self:CreateRow(f.listBox, i)
	end

	f.disenchantButton = CreateTextButton(SECURE_BUTTON_NAME, f, 140, 28, T("DES_DISENCHANT", "Disenchant"), true)
	f.disenchantButton:SetPoint("BOTTOMLEFT", 14, 52)
	f.disenchantButton:RegisterForClicks("AnyUp", "AnyDown")
	f.disenchantButton:SetScript("PostClick", function()
		C_Timer.After(0.2, function()
			if DES and DES.frame and DES.frame:IsShown() then
				DES:ScanBags()
			end
		end)
	end)

	f.bindingLabel = f:CreateFontString(nil, "OVERLAY")
	ApplyFont(f.bindingLabel, 12, "")
	f.bindingLabel:SetPoint("LEFT", f.disenchantButton, "RIGHT", 18, 0)
	f.bindingLabel:SetText(T("DES_BINDING_LABEL", "Key:"))

	f.bindingValue = f:CreateFontString(nil, "OVERLAY")
	ApplyFont(f.bindingValue, 12, "")
	f.bindingValue:SetPoint("LEFT", f.bindingLabel, "RIGHT", 6, 0)
	f.bindingValue:SetTextColor(0.95, 0.82, 0.40, 1)
	f.bindingValue:SetText("")

	f.bindButton = CreateTextButton(nil, f, 90, 22, T("DES_BIND_ASSIGN", "Assign"), false)
	f.bindButton:SetPoint("BOTTOMRIGHT", -14, 56)
	f.bindButton:SetScript("OnClick", function()
		DES:CreateBindCapture()
		DES.bindCapture:Show()
	end)

	f.clearBindButton = CreateTextButton(nil, f, 70, 22, T("DES_BIND_CLEAR", "Clear"), false)
	f.clearBindButton:SetPoint("RIGHT", f.bindButton, "LEFT", -8, 0)
	f.clearBindButton:SetScript("OnClick", function()
		DES:ClearBinding()
	end)

	f.resetExcludedButton = CreateTextButton(nil, f, 120, 22, T("DES_RESET_EXCLUDED", "Reset excluded"), false)
	f.resetExcludedButton:SetPoint("TOPLEFT", f.clearBindButton, "BOTTOMLEFT", 0, -8)
	f.resetExcludedButton:SetScript("OnClick", function()
		DES:ClearExcludedItems()
	end)

	f.selectedText = f:CreateFontString(nil, "OVERLAY")
	ApplyFont(f.selectedText, 11, "")
	f.selectedText:SetPoint("BOTTOMLEFT", 14, 32)
	f.selectedText:SetPoint("RIGHT", -14, 0)
	f.selectedText:SetJustifyH("LEFT")
	f.selectedText:SetText(T("DES_SELECTED_EMPTY", "Selected: —"))

	f.statusText = f:CreateFontString(nil, "OVERLAY")
	ApplyFont(f.statusText, 11, "")
	f.statusText:SetPoint("BOTTOMLEFT", 14, 12)
	f.statusText:SetText((T("DES_STATUS_FORMAT", "Found: %d  •  After filter: %d")):format(0, 0))

	f.noteText = f:CreateFontString(nil, "OVERLAY")
	ApplyFont(f.noteText, 11, "")
	f.noteText:SetPoint("BOTTOMRIGHT", -14, 12)
	f.noteText:SetJustifyH("RIGHT")
	f.noteText:SetText(" ")

	self.frame = f
	self:UpdateBindingText()
	self:UpdateFooter()
	self:UpdateSecureButton()
end

function DES:Toggle()
	if not self.frame then
		self:CreateUI()
	end

	if self.frame:IsShown() then
		self.frame:Hide()
	else
		self.frame:Show()
	end
end

function DES:OnEvent(event, ...)
	if event == "PLAYER_LOGIN" then
		self:CreateUI()
		self:ScanBags()
		self:UpdateBindingText()

	elseif event == "BAG_UPDATE_DELAYED" then
		if self.frame and self.frame:IsShown() then
			self:QueueScanBags(0.05)
		end

	elseif event == "SPELLS_CHANGED" then
		self:UpdateSecureButton()
		if self.frame and self.frame:IsShown() then
			self:UpdateFooter()
		end

	elseif event == "PLAYER_REGEN_ENABLED" then
		if self.pendingSecureRefresh then
			self:UpdateSecureButton()
			if self.frame and self.frame:IsShown() then
				self:UpdateFooter()
			end
		end

		if self.pendingBindingRefresh then
			if self.frame and self.frame:IsShown() then
				self:ApplyTemporaryBinding()
			else
				self:ClearTemporaryBinding()
			end
		end
	end
end

local ev = DES.eventFrame or CreateFrame("Frame")
DES.eventFrame = ev
ev:RegisterEvent("PLAYER_LOGIN")
ev:RegisterEvent("BAG_UPDATE_DELAYED")
ev:RegisterEvent("SPELLS_CHANGED")
ev:RegisterEvent("PLAYER_REGEN_ENABLED")
ev:SetScript("OnEvent", function(_, event, ...)
	DES:OnEvent(event, ...)
end)

SLASH_AHUIDES1 = "/ahuides"
SlashCmdList["AHUIDES"] = function()
	DES:Toggle()
end