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

local CharacterEquipment = {}
SlickUI.CharacterEquipment = CharacterEquipment
SlickUI:RegisterModule("CharacterEquipment", CharacterEquipment)

local SLOT_DEFINITIONS = {
	{ id = 1, button = "CharacterHeadSlot", side = "LEFT", enchant = true },
	{ id = 2, button = "CharacterNeckSlot", side = "LEFT" },
	{ id = 3, button = "CharacterShoulderSlot", side = "LEFT", enchant = true, enchantEdge = "TOP" },
	{ id = 15, button = "CharacterBackSlot", side = "LEFT" },
	{ id = 5, button = "CharacterChestSlot", side = "LEFT", enchant = true, enchantEdge = "TOP" },
	{ id = 4, button = "CharacterShirtSlot", side = "LEFT" },
	{ id = 19, button = "CharacterTabardSlot", side = "LEFT" },
	{ id = 9, button = "CharacterWristSlot", side = "LEFT" },
	{ id = 10, button = "CharacterHandsSlot", side = "RIGHT" },
	{ id = 6, button = "CharacterWaistSlot", side = "RIGHT" },
	{ id = 7, button = "CharacterLegsSlot", side = "RIGHT", enchant = true },
	{ id = 8, button = "CharacterFeetSlot", side = "RIGHT", enchant = true, enchantEdge = "BOTTOM" },
	{ id = 11, button = "CharacterFinger0Slot", side = "RIGHT", enchant = true, enchantEdge = "BOTTOM" },
	{ id = 12, button = "CharacterFinger1Slot", side = "RIGHT", enchant = true },
	{ id = 13, button = "CharacterTrinket0Slot", side = "RIGHT" },
	{ id = 14, button = "CharacterTrinket1Slot", side = "RIGHT" },
	{ id = 16, button = "CharacterMainHandSlot", side = "BOTTOM", enchant = true },
	{ id = 17, button = "CharacterSecondaryHandSlot", side = "BOTTOM", offHand = true },
}

local OFF_HAND_WEAPON_LOCATIONS = {
	INVTYPE_WEAPON = true,
	INVTYPE_WEAPONOFFHAND = true,
	INVTYPE_2HWEAPON = true,
}

local scanTooltip
local slotsCreated = false
local paperDollHooked = false
local refreshQueued = false
local controlsCreated = false
local GetItemLevel
local bankAvailable = false

local TRACKED_LOCATIONS = {
	INVTYPE_HEAD = "HEAD", INVTYPE_NECK = "NECK", INVTYPE_SHOULDER = "SHOULDER",
	INVTYPE_CLOAK = "BACK", INVTYPE_CHEST = "CHEST", INVTYPE_ROBE = "CHEST",
	INVTYPE_WRIST = "WRIST", INVTYPE_HAND = "HANDS", INVTYPE_WAIST = "WAIST",
	INVTYPE_LEGS = "LEGS", INVTYPE_FEET = "FEET", INVTYPE_FINGER = "FINGER",
	INVTYPE_TRINKET = "TRINKET", INVTYPE_WEAPON = "ONE_HAND", INVTYPE_WEAPONMAINHAND = "ONE_HAND",
	INVTYPE_WEAPONOFFHAND = "OFF_HAND", INVTYPE_HOLDABLE = "OFF_HAND",
	INVTYPE_SHIELD = "OFF_HAND", INVTYPE_2HWEAPON = "TWO_HAND", INVTYPE_RANGED = "TWO_HAND",
}

local TRACKED_ORDER = {
	{ key = "HEAD", label = "Head" }, { key = "NECK", label = "Neck" },
	{ key = "SHOULDER", label = "Shoulder" }, { key = "BACK", label = "Back" },
	{ key = "CHEST", label = "Chest" }, { key = "WRIST", label = "Wrist" },
	{ key = "HANDS", label = "Hands" }, { key = "WAIST", label = "Waist" },
	{ key = "LEGS", label = "Legs" }, { key = "FEET", label = "Feet" },
	{ key = "FINGER", label = "Ring" }, { key = "TRINKET", label = "Trinket" },
	{ key = "ONE_HAND", label = "One-hand" }, { key = "OFF_HAND", label = "Off-hand" },
	{ key = "TWO_HAND", label = "Two-hand" },
}

local function CharacterKey()
	local name, realm = UnitFullName("player")
	realm = realm and realm ~= "" and realm or GetRealmName()
	return (name or "Unknown") .. "-" .. (realm or "Unknown")
end

local function GetHighTable()
	local settings = SlickUI.DB and SlickUI.DB.characterEquipment
	if not settings then return nil end
	settings.highestByCharacter = settings.highestByCharacter or {}
	local key = CharacterKey()
	settings.highestByCharacter[key] = settings.highestByCharacter[key] or {}
	return settings.highestByCharacter[key]
end

local function ItemLocationKey(itemLink)
	if not itemLink then return nil end
	local _, _, _, equipLocation = C_Item.GetItemInfoInstant(itemLink)
	return TRACKED_LOCATIONS[equipLocation]
end

local function IsAppropriateEquipment(itemLink)
	if not itemLink or not ItemLocationKey(itemLink) then return false end
	if C_Item and type(C_Item.IsEquippableItem) == "function" and not C_Item.IsEquippableItem(itemLink) then return false end
	return true
end

local function RecordItem(itemLink, icon)
	if not IsAppropriateEquipment(itemLink) then return false end
	local level = GetItemLevel(itemLink)
	local key = ItemLocationKey(itemLink)
	local highs = GetHighTable()
	if not level or not key or not highs then return false end
	local current = highs[key]
	if not current or level > (current.level or 0) then
		highs[key] = { level = level, itemLink = itemLink, icon = icon or select(10, GetItemInfo(itemLink)) }
		return true
	elseif level == (current.level or 0) and not current.itemLink then
		current.itemLink, current.icon = itemLink, icon or select(10, GetItemInfo(itemLink))
	end
	return false
end

function CharacterEquipment:ScanCurrentItems()
	for _, definition in ipairs(SLOT_DEFINITIONS) do
		local link = GetInventoryItemLink("player", definition.id)
		if link then RecordItem(link, GetInventoryItemTexture("player", definition.id)) end
	end
	if C_Container then
		local bagIDs = { 0, 1, 2, 3, 4, 5 }
		if bankAvailable then
			for bagID = 6, 12 do table.insert(bagIDs, bagID) end
			table.insert(bagIDs, -1)
			table.insert(bagIDs, -3)
		end
		for _, bagID in ipairs(bagIDs) do
			for slotID = 1, (C_Container.GetContainerNumSlots(bagID) or 0) do
				local info = C_Container.GetContainerItemInfo(bagID, slotID)
				if info and info.hyperlink then RecordItem(info.hyperlink, info.iconFileID) end
			end
		end
	end
end

function CharacterEquipment:IsHighestItem(itemLink)
	local key = ItemLocationKey(itemLink)
	local level = GetItemLevel(itemLink)
	local highs = GetHighTable()
	return key ~= nil and level ~= nil and highs and highs[key] and level >= (highs[key].level or math.huge)
end

local function CreateHighestBadge(parent)
	local badge = CreateFrame("Frame", nil, parent)
	badge:SetSize(18, 18)
	badge:SetFrameLevel(parent:GetFrameLevel() + 20)
	local circle = badge:CreateTexture(nil, "OVERLAY")
	circle:SetAllPoints()
	circle:SetColorTexture(0.82, 0.03, 0.03, 1)
	if circle.AddMaskTexture then
		local mask = badge:CreateMaskTexture()
		mask:SetAllPoints()
		mask:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
		circle:AddMaskTexture(mask)
	end
	local text = Theme:CreateText(badge, 12, "OVERLAY")
	text:SetPoint("CENTER", 0, 0)
	text:SetText("H")
	text:SetTextColor(1, 1, 1, 1)
	badge:Hide()
	return badge
end

function CharacterEquipment:UpdateBagButtonBadge(button, itemLink)
	if not button then return end
	if not button.SlickUIHighestBadge then
		button.SlickUIHighestBadge = CreateHighestBadge(button)
		button.SlickUIHighestBadge:SetPoint("TOPLEFT", button, "TOPLEFT", -3, 3)
	end
	button.SlickUIHighestBadge:SetShown(itemLink ~= nil and self:IsHighestItem(itemLink) == true)
end

function CharacterEquipment:CreateHighsWindow()
	if self.HighsWindow then return self.HighsWindow end
	local window = CreateFrame("Frame", "SlickUIGearHighsFrame", UIParent, "BackdropTemplate")
	window:SetSize(570, 530)
	window:SetPoint("CENTER")
	window:SetFrameStrata("DIALOG")
	window:SetClampedToScreen(true)
	window:EnableMouse(true)
	window:SetMovable(true)
	window:RegisterForDrag("LeftButton")
	window:SetScript("OnDragStart", window.StartMoving)
	window:SetScript("OnDragStop", window.StopMovingOrSizing)
	Theme:CreatePanel(window)
	window:Hide()

	local title = Theme:CreateText(window, 24)
	title:SetPoint("TOPLEFT", 24, -20)
	title:SetText("Gear Highs")
	local help = Theme:CreateText(window, 11)
	help:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -6)
	help:SetText("Highest appropriate item level SlickUI has recorded for this character.")
	help:SetTextColor(0.78, 0.80, 0.86, 1)
	local close = CreateFrame("Button", nil, window, "UIPanelCloseButton")
	close:SetPoint("TOPRIGHT", -8, -8)

	window.Rows = {}
	for index, definition in ipairs(TRACKED_ORDER) do
		local column = (index - 1) % 3
		local rowIndex = math.floor((index - 1) / 3)
		local row = CreateFrame("Frame", nil, window, "BackdropTemplate")
		row:SetSize(164, 72)
		row:SetPoint("TOPLEFT", 24 + column * 174, -86 - rowIndex * 80)
		Theme:CreatePanel(row)
		row.SlickBackground:SetColorTexture(0.07, 0.08, 0.11, 0.82)
		local icon = row:CreateTexture(nil, "ARTWORK")
		icon:SetSize(42, 42)
		icon:SetPoint("LEFT", 9, 0)
		local badge = CreateHighestBadge(row)
		badge:SetPoint("TOPLEFT", icon, "TOPLEFT", -4, 4)
		local label = Theme:CreateText(row, 10)
		label:SetPoint("TOPLEFT", icon, "TOPRIGHT", 8, -2)
		label:SetText(definition.label)
		label:SetTextColor(0.78, 0.80, 0.86, 1)
		local level = Theme:CreateText(row, 18)
		level:SetPoint("BOTTOMLEFT", icon, "BOTTOMRIGHT", 8, 2)
		level:SetTextColor(1, 0.82, 0.16, 1)
		row.Icon, row.Level, row.Badge, row.Key = icon, level, badge, definition.key
		row:EnableMouse(true)
		row:SetScript("OnEnter", function(self)
			local entry = GetHighTable() and GetHighTable()[self.Key]
			if entry and entry.itemLink then GameTooltip:SetOwner(self, "ANCHOR_RIGHT"); GameTooltip:SetHyperlink(entry.itemLink); GameTooltip:Show() end
		end)
		row:SetScript("OnLeave", GameTooltip_Hide)
		window.Rows[index] = row
	end
	self.HighsWindow = window
	return window
end

function CharacterEquipment:RefreshHighsWindow()
	if not self.HighsWindow or not self.HighsWindow:IsShown() then return end
	local highs = GetHighTable() or {}
	for _, row in ipairs(self.HighsWindow.Rows) do
		local entry = highs[row.Key]
		row.Icon:SetTexture(entry and entry.icon or 134400)
		row.Icon:SetDesaturated(entry == nil)
		row.Icon:SetAlpha(entry and 1 or 0.25)
		row.Badge:SetShown(entry ~= nil)
		row.Level:SetText(entry and tostring(entry.level) or "--")
	end
end

function CharacterEquipment:ToggleHighsWindow()
	local window = self:CreateHighsWindow()
	if window:IsShown() then window:Hide() else self:ScanCurrentItems(); self:RefreshHighsWindow(); window:Show() end
end

function CharacterEquipment:GetHighsSummary()
	local highs, best, count = GetHighTable() or {}, 0, 0
	for _, entry in pairs(highs) do best, count = math.max(best, entry.level or 0), count + 1 end
	return count > 0 and ("Gear Highs: " .. count .. " slots") or "Gear Highs: learning"
end

function CharacterEquipment:BuildHighsTooltip(tooltip)
	tooltip:AddLine("Gear Highs", unpack(Theme.Colors.goldText))
	tooltip:AddLine("Highest appropriate item levels recorded for this character.", 1, 1, 1, true)
	tooltip:AddLine("The red H marks items matching a recorded slot high.", 1, 0.35, 0.30, true)
	tooltip:AddLine("Records persist after an item is sold. Bank items are learned when accessible.", 0.55, 0.88, 1, true)
end

local function CreateDisplayCheckBox(name, label)
	local checkBox = CreateFrame("CheckButton", name, PaperDollFrame, "UICheckButtonTemplate")
	checkBox:SetSize(122, 22)
	checkBox:SetFrameStrata("HIGH")
	checkBox:SetFrameLevel(100)
	checkBox.Label = Theme:CreateText(checkBox, 10, "OVERLAY")
	checkBox.Label:SetPoint("LEFT", checkBox, "LEFT", 25, 0)
	checkBox.Label:SetText(label)
	checkBox.Label:SetTextColor(1, 0.82, 0.16, 1)
	return checkBox
end

local function GetScanTooltip()
	if scanTooltip then
		return scanTooltip
	end

	scanTooltip = CreateFrame("GameTooltip", "SlickUICharacterEquipmentScanTooltip", UIParent, "GameTooltipTemplate")
	scanTooltip:SetOwner(UIParent, "ANCHOR_NONE")
	return scanTooltip
end

local function GetEnchantID(itemLink)
	local itemString = itemLink and itemLink:match("|H(item:[^|]+)|h")
	if not itemString then
		return 0
	end

	local _, _, enchantID = strsplit(":", itemString)
	return tonumber(enchantID) or 0
end

local function GetEnchantName(itemLink)
	if GetEnchantID(itemLink) == 0 then
		return nil
	end

	local tooltip = GetScanTooltip()
	tooltip:ClearLines()
	tooltip:SetHyperlink(itemLink)

	local pattern = type(ENCHANTED_TOOLTIP_LINE) == "string"
		and ENCHANTED_TOOLTIP_LINE:gsub("%%s", "(.+)")
		or nil
	for lineIndex = 2, tooltip:NumLines() do
		local line = _G[tooltip:GetName() .. "TextLeft" .. lineIndex]
		local text = line and line:GetText()
		if type(text) == "string" and pattern then
			local enchantName = text:match(pattern)
			if enchantName and enchantName ~= "" then
				return enchantName
			end
		end
	end

	return "Enchanted"
end

GetItemLevel = function(itemLink)
	if not itemLink then
		return nil
	end

	if C_Item and type(C_Item.GetDetailedItemLevelInfo) == "function" then
		return C_Item.GetDetailedItemLevelInfo(itemLink)
	end
	if type(GetDetailedItemLevelInfo) == "function" then
		return GetDetailedItemLevelInfo(itemLink)
	end

	local _, _, _, itemLevel = GetItemInfo(itemLink)
	return itemLevel
end

local function OffHandCanBeEnchanted(itemLink)
	if not itemLink then
		return false
	end

	local _, _, _, equipLocation, _, classID, subClassID = C_Item.GetItemInfoInstant(itemLink)
	if classID ~= Enum.ItemClass.Weapon or not OFF_HAND_WEAPON_LOCATIONS[equipLocation] then
		return false
	end
	if Enum.ItemWeaponSubclass and subClassID == Enum.ItemWeaponSubclass.Fishingpole then
		return false
	end
	return true
end

local function CreateSlotOverlay(definition)
	local button = _G[definition.button]
	if not button or button.SlickUIEquipmentOverlay then
		return button ~= nil
	end

	local overlay = CreateFrame("Frame", nil, button)
	overlay:SetAllPoints(button)
	overlay:SetFrameLevel(button:GetFrameLevel() + 8)
	overlay:EnableMouse(false)

	local itemLevel = Theme:CreateText(overlay, 13, "OVERLAY")
	itemLevel:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -2, 2)
	itemLevel:SetJustifyH("RIGHT")
	itemLevel:SetTextColor(1, 0.84, 0.18, 1)
	itemLevel:SetDrawLayer("OVERLAY", 7)

	local enchant = Theme:CreateText(overlay, 10, "OVERLAY")
	enchant:SetWidth(150)
	enchant:SetWordWrap(false)
	if definition.side == "LEFT" then
		if definition.enchantEdge == "TOP" then
			enchant:SetPoint("TOPLEFT", button, "TOPRIGHT", 7, -1)
		elseif definition.enchantEdge == "BOTTOM" then
			enchant:SetPoint("BOTTOMLEFT", button, "BOTTOMRIGHT", 7, 1)
		else
			enchant:SetPoint("LEFT", button, "RIGHT", 7, 0)
		end
		enchant:SetJustifyH("LEFT")
	elseif definition.side == "RIGHT" then
		if definition.enchantEdge == "TOP" then
			enchant:SetPoint("TOPRIGHT", button, "TOPLEFT", -7, -1)
		elseif definition.enchantEdge == "BOTTOM" then
			enchant:SetPoint("BOTTOMRIGHT", button, "BOTTOMLEFT", -7, 1)
		else
			enchant:SetPoint("RIGHT", button, "LEFT", -7, 0)
		end
		enchant:SetJustifyH("RIGHT")
	else
		enchant:SetPoint("BOTTOM", button, "TOP", 0, 5)
		enchant:SetJustifyH("CENTER")
	end

	button.SlickUIEquipmentOverlay = overlay
	button.SlickUIItemLevel = itemLevel
	button.SlickUIEnchantStatus = enchant
	return true
end

function CharacterEquipment:CreateControls()
	if controlsCreated or not PaperDollFrame or not CharacterHeadSlot then
		return controlsCreated
	end

	local itemLevels = CreateDisplayCheckBox("SlickUIShowCharacterItemLevels", "Show item levels")
	itemLevels:SetPoint("BOTTOMLEFT", CharacterHeadSlot, "TOPRIGHT", 12, -4)
	itemLevels:SetScript("OnClick", function(self)
		SlickUI.DB.characterEquipment.showItemLevels = self:GetChecked() == true
		CharacterEquipment:Refresh()
	end)

	local enchants = CreateDisplayCheckBox("SlickUIShowCharacterEnchants", "Show enchants")
	enchants:SetPoint("BOTTOMRIGHT", CharacterHandsSlot, "TOPLEFT", -4, -4)
	enchants:SetScript("OnClick", function(self)
		SlickUI.DB.characterEquipment.showEnchants = self:GetChecked() == true
		CharacterEquipment:Refresh()
	end)

	self.ItemLevelsCheckBox = itemLevels
	self.EnchantsCheckBox = enchants
	controlsCreated = true
	return true
end

function CharacterEquipment:CreateOverlays()
	if slotsCreated then
		return true
	end

	local found = 0
	for _, definition in ipairs(SLOT_DEFINITIONS) do
		if CreateSlotOverlay(definition) then
			found = found + 1
		end
	end
	slotsCreated = found == #SLOT_DEFINITIONS
	self:CreateControls()

	if PaperDollFrame and not paperDollHooked then
		PaperDollFrame:HookScript("OnShow", function()
			CharacterEquipment:QueueRefresh()
		end)
		paperDollHooked = true
	end

	return slotsCreated
end

function CharacterEquipment:Refresh()
	refreshQueued = false
	self:ScanCurrentItems()
	if not self:CreateOverlays() then
		return
	end

	local settings = SlickUI.DB.characterEquipment
	if self.ItemLevelsCheckBox then
		self.ItemLevelsCheckBox:SetChecked(settings.showItemLevels == true)
	end
	if self.EnchantsCheckBox then
		self.EnchantsCheckBox:SetChecked(settings.showEnchants == true)
	end

	for _, definition in ipairs(SLOT_DEFINITIONS) do
		local button = _G[definition.button]
		local itemLevelText = button and button.SlickUIItemLevel
		local enchantText = button and button.SlickUIEnchantStatus
		local itemLink = GetInventoryItemLink("player", definition.id)

		if itemLevelText then
			itemLevelText:SetShown(settings.showItemLevels == true)
			local itemLevel = GetItemLevel(itemLink)
			itemLevelText:SetText(itemLevel and tostring(itemLevel) or "")
		end

		if enchantText then
			enchantText:SetShown(settings.showEnchants == true)
			enchantText:SetText("")
			local enchantable = definition.enchant == true
				or (definition.offHand and OffHandCanBeEnchanted(itemLink))
			if itemLink and enchantable then
				local enchantName = GetEnchantName(itemLink)
				if enchantName then
					enchantText:SetText(enchantName)
					enchantText:SetTextColor(0.20, 1.00, 0.34, 1)
				else
					enchantText:SetText("MISSING ENCHANT")
					enchantText:SetTextColor(1.00, 0.18, 0.12, 1)
				end
			end
		end
	end
	if SlickUI.Bags and SlickUI.Bags.Buttons then
		for _, button in pairs(SlickUI.Bags.Buttons) do
			local info = C_Container.GetContainerItemInfo(button.BagID, button.SlotID)
			self:UpdateBagButtonBadge(button, info and info.hyperlink)
		end
	end
	self:RefreshHighsWindow()
end

function CharacterEquipment:QueueRefresh()
	if refreshQueued then
		return
	end
	refreshQueued = true
	C_Timer.After(0, function()
		CharacterEquipment:Refresh()
	end)
	C_Timer.After(0.35, function()
		CharacterEquipment:Refresh()
	end)
end

function CharacterEquipment:Initialize()
	local eventFrame = CreateFrame("Frame")
	eventFrame:RegisterEvent("ADDON_LOADED")
	eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
	eventFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
	eventFrame:RegisterEvent("UNIT_INVENTORY_CHANGED")
	eventFrame:RegisterEvent("GET_ITEM_INFO_RECEIVED")
	eventFrame:RegisterEvent("BAG_UPDATE_DELAYED")
	eventFrame:RegisterEvent("BANKFRAME_OPENED")
	eventFrame:SetScript("OnEvent", function(_, event, arg1)
		if event == "UNIT_INVENTORY_CHANGED" and arg1 ~= "player" then
			return
		end
		if event == "BANKFRAME_OPENED" then bankAvailable = true end
		CharacterEquipment:QueueRefresh()
	end)

	self.EventFrame = eventFrame
	self:QueueRefresh()
end
