--## Author: Josh "Kkthnx" Russell ## Version: 1.0.6

-- REASON: Localize globals for performance and avoid global lookups in high-frequency operations.
local CreateFrame = CreateFrame
local EnumerateFrames = EnumerateFrames
local RegisterAttributeDriver = RegisterAttributeDriver
local RegisterStateDriver = RegisterStateDriver
local UIParent = UIParent
local _G = _G
local getmetatable = getmetatable
local select = select
local tonumber = tonumber
local type = type

-- REASON: Provide a hidden parent for frames that need to be effectively disabled.
local uiFrameHider = CreateFrame("Frame", "Kkthnx_UIFrameHider", UIParent, "SecureHandlerAttributeTemplate")
uiFrameHider:Hide()
uiFrameHider:SetPoint("TOPLEFT", 0, 0)
uiFrameHider:SetPoint("BOTTOMRIGHT", 0, 0)
RegisterAttributeDriver(uiFrameHider, "state-visibility", "hide")

-- REASON: Hide specific frames during pet battles to reduce visual clutter.
local petBattleHider = CreateFrame("Frame", "Kkthnx_PetBattleHider", UIParent, "SecureHandlerStateTemplate")
petBattleHider:SetAllPoints()
petBattleHider:SetFrameStrata("LOW")
RegisterStateDriver(petBattleHider, "visibility", "[petbattle] hide; show")

-- REASON: Completely disable an object by unregistering events and hiding it.
local function killObject(object)
	if object.UnregisterAllEvents then
		object:UnregisterAllEvents()
		object:SetParent(uiFrameHider)
	else
		object.Show = object.Hide
	end

	object:Hide()
end

local BLIZZARD_TEXTURES = {
	"Inset",
	"inset",
	"InsetFrame",
	"LeftInset",
	"RightInset",
	"NineSlice",
	"BG",
	"Bg",
	"border",
	"Border",
	"Background",
	"BorderFrame",
	"bottomInset",
	"BottomInset",
	"bgLeft",
	"bgRight",
	"FilligreeOverlay",
	"PortraitOverlay",
	"ArtOverlayFrame",
	"Portrait",
	"portrait",
	"ScrollFrameBorder",
	"ScrollUpBorder",
	"ScrollDownBorder",
	"TitleWidgetContainer",
}

local function processRegions(shouldKill, ...)
	for i = 1, select("#", ...) do
		local region = select(i, ...)
		if region and region.IsObjectType and region:IsObjectType("Texture") then
			if shouldKill and type(shouldKill) == "boolean" then
				killObject(region)
			elseif tonumber(shouldKill) then
				if shouldKill == 0 then
					region:SetAlpha(0)
				elseif i ~= shouldKill then
					-- PERF: Setting texture to empty string is faster than hiding for many regions.
					region:SetTexture("")
				end
			else
				region:SetTexture("")
			end
		end
	end
end

-- REASON: Remove default Blizzard textures from frames for a cleaner UI look.
local function stripTextures(object, shouldKill)
	local frameName = object.GetName and object:GetName()
	for i = 1, #BLIZZARD_TEXTURES do
		local texture = BLIZZARD_TEXTURES[i]
		local blizzFrame = object[texture] or (frameName and _G[frameName .. texture])
		if blizzFrame then
			stripTextures(blizzFrame, shouldKill)
		end
	end

	if object.GetRegions then
		processRegions(shouldKill, object:GetRegions())
	end
end

-- REASON: Extend the base WoW API with custom utility functions.
local function addApi(object)
	local mt = getmetatable(object).__index

	if not object.Kill then
		mt.Kill = killObject
	end

	if not object.StripTextures then
		mt.StripTextures = stripTextures
	end
end

-- REASON: Apply the custom API to all existing and future frames.
local handledFrames = { Frame = true }
local baseFrame = CreateFrame("Frame")
addApi(baseFrame)
addApi(baseFrame:CreateTexture())
addApi(baseFrame:CreateFontString())
addApi(baseFrame:CreateMaskTexture())

local currentObject = EnumerateFrames()
while currentObject do
	if not currentObject:IsForbidden() and not handledFrames[currentObject:GetObjectType()] then
		addApi(currentObject)
		handledFrames[currentObject:GetObjectType()] = true
	end

	currentObject = EnumerateFrames(currentObject)
end


-- REASON: Localize globals for performance and to avoid global lookups.
local _G = _G
local CreateFrame = CreateFrame
local CharacterFrame = _G.CharacterFrame
local CharacterHandsSlot = _G.CharacterHandsSlot
local CharacterHeadSlot = _G.CharacterHeadSlot
local CharacterMainHandSlot = _G.CharacterMainHandSlot
local CharacterModelScene = _G.CharacterModelScene
local CharacterSecondaryHandSlot = _G.CharacterSecondaryHandSlot
local CharacterStatsPane = _G.CharacterStatsPane
local HideUIPanel = HideUIPanel
local InCombatLockdown = InCombatLockdown
local CharacterFrameInsetRight = _G.CharacterFrameInsetRight
local PaperDollFrame = _G.PaperDollFrame
local PaperDollItemsFrame = _G.PaperDollItemsFrame
local UnitClass = UnitClass
local hooksecurefunc = hooksecurefunc
local select = select

-- REASON: Manage layout and appearance of the Character Frame.
local module = CreateFrame("Frame", "Kkthnx_BetterCharacterFrame")
module:RegisterEvent("PLAYER_LOGIN")

module:SetScript("OnEvent", function(self, event, ...)
	-- REASON: Ensure the frame is hidden on login to apply initial customizations correctly.
	if CharacterFrame:IsShown() then
		HideUIPanel(CharacterFrame)
	end

	-- REASON: Remove default backgrounds and borders for a cleaner look.
	CharacterModelScene:DisableDrawLayer("BACKGROUND")
	CharacterModelScene:DisableDrawLayer("BORDER")
	CharacterModelScene:DisableDrawLayer("OVERLAY")
	CharacterModelScene:StripTextures(true)

	-- REASON: Standardize item slot sizes and appearance.
	local function styleItems(...)
		for i = 1, select("#", ...) do
			local slot = select(i, ...)
			if slot:IsObjectType("Button") or slot:IsObjectType("ItemButton") then
				slot:StripTextures()
				slot:SetSize(37, 37)
			end
		end
	end
	styleItems(PaperDollItemsFrame:GetChildren())

	-- REASON: Position equipment slots and the model scene within the inset.
	CharacterHeadSlot:SetPoint("TOPLEFT", CharacterFrame.Inset, "TOPLEFT", 6, -6)
	CharacterHandsSlot:SetPoint("TOPRIGHT", CharacterFrame.Inset, "TOPRIGHT", -6, -6)
	CharacterMainHandSlot:SetPoint("BOTTOMLEFT", CharacterFrame.Inset, "BOTTOMLEFT", 176, 5)
	CharacterSecondaryHandSlot:ClearAllPoints()
	CharacterSecondaryHandSlot:SetPoint("BOTTOMRIGHT", CharacterFrame.Inset, "BOTTOMRIGHT", -176, 5)

	CharacterModelScene:SetSize(0, 0)
	CharacterModelScene:ClearAllPoints()
	CharacterModelScene:SetPoint("TOPLEFT", CharacterFrame.Inset, 0, 0)
	CharacterModelScene:SetPoint("BOTTOMRIGHT", CharacterFrame.Inset, 0, 20)

	-- REASON: Dynamically adjust the Character Frame size and background when switching tabs.
	hooksecurefunc(CharacterFrame, "UpdateSize", function()
		-- WARNING: Avoid modification if in combat as the frame is secure.
		if InCombatLockdown() then
			return
		end

		if CharacterFrame.activeSubframe == "PaperDollFrame" then
			CharacterFrame:SetSize(640, 431)
			CharacterFrame.Inset:SetPoint("BOTTOMRIGHT", CharacterFrame, "BOTTOMLEFT", 432, 4)

			local _, class = UnitClass("player")
			CharacterFrame.Inset.Bg:SetTexture("Interface\\DressUpFrame\\DressingRoom" .. class)
			CharacterFrame.Inset.Bg:SetTexCoord(1 / 512, 479 / 512, 46 / 512, 455 / 512)
			CharacterFrame.Inset.Bg:SetHorizTile(false)
			CharacterFrame.Inset.Bg:SetVertTile(false)

			CharacterFrame.Background:Hide()
		else
			CharacterFrame.Background:Show()
		end
	end)

	-- REASON: Enhance the item level display for better readability.
	local charItemLevelValue = CharacterStatsPane.ItemLevelFrame.Value
	charItemLevelValue:SetFont(select(1, charItemLevelValue:GetFont()), 20, select(3, ""))
	charItemLevelValue:SetShadowOffset(1, -1)

	-- REASON: Clean up the Title Manager list appearance.
	local function styleTitleChildren(...)
		for i = 1, select("#", ...) do
			local child = select(i, ...)
			if child and not child.styled then
				child:DisableDrawLayer("BACKGROUND")
				child.styled = true
			end
		end
	end

	hooksecurefunc(PaperDollFrame.TitleManagerPane.ScrollBox, "Update", function(scrollBox)
		styleTitleChildren(scrollBox.ScrollTarget:GetChildren())
	end)

	-- REASON: Re-anchor the class background to fit the new layout.
	CharacterStatsPane.ClassBackground:ClearAllPoints()
	CharacterStatsPane.ClassBackground:SetHeight(CharacterStatsPane.ClassBackground:GetHeight() + 6)
	CharacterStatsPane.ClassBackground:SetParent(CharacterFrameInsetRight)
	CharacterStatsPane.ClassBackground:SetPoint("CENTER")
end)


-- REASON: Localize globals for performance and to avoid global lookups.
local _G = _G
local CreateFrame = CreateFrame
local HideUIPanel = HideUIPanel
local InCombatLockdown = InCombatLockdown
local PanelTemplates_GetSelectedTab = PanelTemplates_GetSelectedTab
local UnitClass = UnitClass
local hooksecurefunc = hooksecurefunc
local select = select
-- REASON: issecretvalue only exists on Midnight (12.0+); guarded so 11.x clients don't error.
local issecretvalue = issecretvalue

-- REASON: Manage layout and appearance of the Inspect UI.
local module = CreateFrame("Frame", "Kkthnx_BetterInspectUI")
module:RegisterEvent("ADDON_LOADED")

module:SetScript("OnEvent", function(self, event, ...)
	local addon = ...

	-- REASON: Wait for the Blizzard_InspectUI addon to be loaded before applying customizations.
	if addon ~= "Blizzard_InspectUI" then
		return
	end

	-- REASON: Ensure the frame is hidden when customizations are first applied.
	local InspectFrame = _G.InspectFrame
	local InspectHandsSlot = _G.InspectHandsSlot
	local InspectHeadSlot = _G.InspectHeadSlot
	local InspectMainHandSlot = _G.InspectMainHandSlot
	local InspectModelFrame = _G.InspectModelFrame
	local InspectPaperDollItemsFrame = _G.InspectPaperDollItemsFrame
	local InspectSecondaryHandSlot = _G.InspectSecondaryHandSlot

	if InspectFrame:IsShown() then
		HideUIPanel(InspectFrame)
	end

	-- REASON: Reposition talent button for better layout integration.
	InspectPaperDollItemsFrame.InspectTalents:ClearAllPoints()
	InspectPaperDollItemsFrame.InspectTalents:SetPoint("TOPRIGHT", InspectFrame, "BOTTOMRIGHT", 0, -1)

	InspectModelFrame:StripTextures(true)

	-- REASON: Standardize item slot sizes and appearance in the inspect frame.
	-- BUGFIX: Exclude the InspectTalents button; it is a child Button of
	-- InspectPaperDollItemsFrame and was being resized to 37x37 like an item slot.
	local inspectTalents = InspectPaperDollItemsFrame.InspectTalents
	local function styleItems(...)
		for i = 1, select("#", ...) do
			local slot = select(i, ...)
			if slot ~= inspectTalents and (slot:IsObjectType("Button") or slot:IsObjectType("ItemButton")) then
				slot:StripTextures()
				slot:SetSize(37, 37)
			end
		end
	end
	styleItems(InspectPaperDollItemsFrame:GetChildren())

	-- REASON: Position equipment slots and the model scene within the inspect frame.
	InspectHeadSlot:SetPoint("TOPLEFT", InspectFrame.Inset, "TOPLEFT", 6, -6)
	InspectHandsSlot:SetPoint("TOPRIGHT", InspectFrame.Inset, "TOPRIGHT", -6, -6)
	InspectMainHandSlot:SetPoint("BOTTOMLEFT", InspectFrame.Inset, "BOTTOMLEFT", 175, 5)
	InspectSecondaryHandSlot:ClearAllPoints()
	InspectSecondaryHandSlot:SetPoint("BOTTOMRIGHT", InspectFrame.Inset, "BOTTOMRIGHT", -175, 5)

	InspectModelFrame:SetSize(0, 0)
	InspectModelFrame:ClearAllPoints()
	InspectModelFrame:SetPoint("TOPLEFT", InspectFrame.Inset, 0, 0)
	InspectModelFrame:SetPoint("BOTTOMRIGHT", InspectFrame.Inset, 0, 30)
	InspectModelFrame:SetCamDistanceScale(1.1)

	-- REASON: Dynamically adjust the Inspect Frame size and background when switching tabs.
	local onInspectSwitchTabs = function(newID)
		-- WARNING: Avoid modification if in combat as the frame is secure.
		if InCombatLockdown() then
			return
		end

		local tabID = newID or PanelTemplates_GetSelectedTab(InspectFrame)
		if tabID == 1 then
			InspectFrame:SetSize(438, 431)
			InspectFrame.Inset:SetPoint("BOTTOMRIGHT", InspectFrame, "BOTTOMLEFT", 432, 4)

			-- REASON: On Midnight, unit data can be a Secret Value inside instances.
			-- issecretvalue() never errors and is checked first so the truthiness test
			-- and string concatenation below can never touch a Secret. Guarded with
			-- "not issecretvalue or" so 11.x clients (no such global) still work.
			local _, targetClass = UnitClass("target")
			if (not issecretvalue or not issecretvalue(targetClass)) and targetClass then
				InspectFrame.Inset.Bg:SetTexture("Interface\\DressUpFrame\\DressingRoom" .. targetClass)
				InspectFrame.Inset.Bg:SetTexCoord(0.00195312, 0.935547, 0.00195312, 0.978516)
				InspectFrame.Inset.Bg:SetHorizTile(false)
				InspectFrame.Inset.Bg:SetVertTile(false)
			end
		else
			InspectFrame:SetSize(338, 424)
			InspectFrame.Inset:SetPoint("BOTTOMRIGHT", InspectFrame, "BOTTOMLEFT", 332, 4)

			InspectFrame.Inset.Bg:SetTexture("Interface\\FrameGeneral\\UI-Background-Marble", "REPEAT", "REPEAT")
			InspectFrame.Inset.Bg:SetTexCoord(0, 1, 0, 1)
			InspectFrame.Inset.Bg:SetHorizTile(true)
			InspectFrame.Inset.Bg:SetVertTile(true)
		end
	end

	-- REASON: Ensure layout updates whenever the user switches tabs.
	hooksecurefunc("InspectSwitchTabs", onInspectSwitchTabs)

	-- REASON: Apply the customizations immediately for the first tab.
	onInspectSwitchTabs(1)

	self:UnregisterEvent(event)
end)
