local PT = AhUI
if not PT then return end

local G  = PT.ProfessionGuides
local UI = {}
local Shopping = PT.ProfessionGuides_Shopping
local Waypoints = PT.ProfessionGuides_Waypoints
local State = PT.ProfessionGuides_State
local FONT          = AhUI.FONT
local DEFAULT_SCALE = AhUI.SCALE_DEFAULT
local SCALE_STEP    = AhUI.SCALE_STEP
local SCALE_MIN     = AhUI.SCALE_MIN
local SCALE_MAX     = AhUI.SCALE_MAX
local INFO_ATLAS = "help-i"
local FRAME_PADDING_TOP    = 16
local FRAME_PADDING_BOTTOM = 16
local FRAME_MIN_HEIGHT     = 200
local TEXT_SAFE_WIDTH = 270
local LINE_SPACING = 3
local Tooltip = PT.Tooltip
local Epochs = PT.ProfessionGuides_Epochs or {}
local L = PT.L or {}
local lastOpenedSkillLine = nil

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

local _itemLinkCache = {}
local function GetCachedItemLink(itemID)
    if _itemLinkCache[itemID] then return _itemLinkCache[itemID] end
    local link = select(2, GetItemInfo(itemID))
    if link then _itemLinkCache[itemID] = link end
    return link or ("item:" .. itemID)
end

local _itemNameCache = {}
local function GetCachedItemName(itemID)
    if _itemNameCache[itemID] then return _itemNameCache[itemID] end
    local name = GetItemInfo(itemID)
    if name then _itemNameCache[itemID] = name end
    return name or ("item:" .. itemID)
end

local enchantTargetLocation = nil

local function GetRecipeIDBySpellID(spellID)
    local recipeIDs = C_TradeSkillUI.GetAllRecipeIDs()
    if not recipeIDs then return nil end

    for _, recipeID in ipairs(recipeIDs) do
        local info = C_TradeSkillUI.GetRecipeInfo(recipeID)
        if info and info.spellID == spellID then
            return recipeID
        end
    end
end

local function DoCraft(spellID, count)
    local recipeInfo = C_TradeSkillUI.GetRecipeInfo(spellID)
    if not recipeInfo then
        print(T("PG_ERR_GET_RECIPE_INFO", "AhUI: GetRecipeInfo returned nil for spellID:"), spellID)
        return
    end

    if recipeInfo.isEnchantingRecipe then
        local target = nil
        for bag = 0, NUM_BAG_SLOTS do
            local slots = C_Container.GetContainerNumSlots(bag)
            if slots then
                for slot = 1, slots do
                    if C_Container.GetContainerItemID(bag, slot) == 38682 then
                        target = ItemLocation:CreateFromBagAndSlot(bag, slot)
                        break
                    end
                end
            end
            if target then break end
        end
        target = target or enchantTargetLocation
        if not target then
            print(T("PG_ERR_NO_ENCHANT_SCROLL", "AhUI: no enchant vellum found in bags"))
            return
        end
        C_TradeSkillUI.CraftEnchant(spellID, count or 1, nil, target)
    else
        C_TradeSkillUI.CraftRecipe(spellID, count or 1)
    end
end

local _craftPendingSpellID = nil
local _craftPendingCount   = nil
local _craftWaitFrame = nil

local function GetSpellNameByID(spellID)
    if not spellID or spellID <= 0 then return nil end

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

    if GetSpellInfo then
        return GetSpellInfo(spellID)
    end

    return nil
end

local function GetSpellSubTextByID(spellID)
    if not spellID or spellID <= 0 then return nil end

    if C_Spell and C_Spell.GetSpellSubtext then
        return C_Spell.GetSpellSubtext(spellID)
    end

    if GetSpellSubtext then
        return GetSpellSubtext(spellID)
    end

    return nil
end


local function CraftRecipe(spellID, count)
    if not C_TradeSkillUI then return end
    if not spellID then return end

    local recipeIDs = C_TradeSkillUI.GetAllRecipeIDs()
    if recipeIDs and #recipeIDs > 0 then
        DoCraft(spellID, count)
        return
    end

    _craftPendingSpellID = spellID
    _craftPendingCount   = count or 1

    local skillLineID = G:GetSelectedSkillLine()
    if skillLineID then
        C_TradeSkillUI.OpenTradeSkill(skillLineID)
    end

    if not _craftWaitFrame then
        _craftWaitFrame = CreateFrame("Frame")
        _craftWaitFrame:RegisterEvent("TRADE_SKILL_LIST_UPDATE")
        _craftWaitFrame:SetScript("OnEvent", function()
            if _craftPendingSpellID then
                local sid = _craftPendingSpellID
                local cnt = _craftPendingCount
                _craftPendingSpellID = nil
                _craftPendingCount   = nil
                DoCraft(sid, cnt)
            end
        end)
    end
end


local function UpdateBuyButtonVisibility()
    if not UI.frame or not UI.frame.buyBtn then return end

    local skillLineID = G:GetSelectedSkillLine()
    local step = skillLineID and G:GetCurrentStepBySkillLine(skillLineID)

    UI.frame.buyBtn:SetShown(
        Shopping:CanBuy(skillLineID)
        and not (step and step.disablesShopping)
    )
end

local function CreateIconBorder(iconHolder)
    local holder = CreateFrame("Frame", nil, iconHolder, "BackdropTemplate")
    holder:SetFrameLevel(iconHolder:GetFrameLevel() + 10)

    holder:SetPoint("TOPLEFT", iconHolder, "TOPLEFT", -2, 2)
    holder:SetPoint("BOTTOMRIGHT", iconHolder, "BOTTOMRIGHT", 2, -2)

    holder:SetBackdrop({
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 2,
    })

    holder:SetBackdropBorderColor(0, 0, 0, 1)
    iconHolder.border = holder
end

local function CreateButtonBorder(button)
    local border = CreateFrame("Frame", nil, button, "BackdropTemplate")
    border:SetPoint("TOPLEFT", -1, 1)
    border:SetPoint("BOTTOMRIGHT", 1, -1)
    border:SetFrameLevel(button:GetFrameLevel() - 1)

    border:SetBackdrop({
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
    })

    border:SetBackdropBorderColor(1, 1, 1, 0.15)
    button.border = border
end

local function ApplyHoverHighlight(button, bgTexture, textObject, opts)
    if not button or not bgTexture then return end

    opts = opts or {}
    local normalAlpha = opts.normalAlpha or 0.06
    local hoverAlpha = opts.hoverAlpha or 0.12
    local normalText = opts.normalTextColor or { 1, 1, 1 }
    local hoverText = opts.hoverTextColor or { 1, 1, 1 }

    button._ahuiHoverBG = bgTexture
    button._ahuiHoverText = textObject
    button._ahuiHoverNormalAlpha = normalAlpha
    button._ahuiHoverHoverAlpha = hoverAlpha
    button._ahuiHoverNormalText = normalText
    button._ahuiHoverHoverText = hoverText

    if not button.border then
        CreateButtonBorder(button)
    end

    bgTexture:SetColorTexture(1, 1, 1, normalAlpha)
    if textObject then
        textObject:SetTextColor(normalText[1], normalText[2], normalText[3])
    end

    local oldOnEnter = button:GetScript("OnEnter")
    local oldOnLeave = button:GetScript("OnLeave")

    button:SetScript("OnEnter", function(self, ...)
        local enabled = (not self.IsEnabled) or self:IsEnabled()

        if self._ahuiHoverBG then
            self._ahuiHoverBG:SetColorTexture(
                1, 1, 1,
                enabled and self._ahuiHoverHoverAlpha or self._ahuiHoverNormalAlpha
            )
        end

        if self.border then
            self.border:SetBackdropBorderColor(1, 1, 1, enabled and 0.35 or 0.15)
        end

        if self._ahuiHoverText then
            local color = enabled and self._ahuiHoverHoverText or self._ahuiHoverNormalText
            self._ahuiHoverText:SetTextColor(color[1], color[2], color[3])
        end

        if oldOnEnter then
            oldOnEnter(self, ...)
        end
    end)

    button:SetScript("OnLeave", function(self, ...)
        if self._ahuiHoverBG then
            self._ahuiHoverBG:SetColorTexture(1, 1, 1, self._ahuiHoverNormalAlpha)
        end

        if self.border then
            self.border:SetBackdropBorderColor(1, 1, 1, 0.15)
        end

        if self._ahuiHoverText then
            local color = self._ahuiHoverNormalText
            self._ahuiHoverText:SetTextColor(color[1], color[2], color[3])
        end

        if oldOnLeave then
            oldOnLeave(self, ...)
        end
    end)
end

local function ShowGameTooltipAtCursor(setupFunc, offsetX, offsetY)
    local x, y = GetCursorPosition()
    local scale = UIParent:GetEffectiveScale()
    x = (x / scale) + (offsetX or 16)
    y = (y / scale) + (offsetY or 16)

    GameTooltip:SetOwner(UIParent, "ANCHOR_NONE")
    GameTooltip:ClearAllPoints()
    GameTooltip:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", x, y)

    if setupFunc then
        setupFunc(GameTooltip)
    end

    GameTooltip:Show()
end

local _pgConfigCache = nil
local function GetPGConfig()
    if _pgConfigCache then return _pgConfigCache end
    AhUI_CharConfig = AhUI_CharConfig or {}
    AhUI_CharConfig.professionGuide = AhUI_CharConfig.professionGuide or {}
    _pgConfigCache = AhUI_CharConfig.professionGuide
    return _pgConfigCache
end

local function GetCheapestAnyOfUIChoice(reagent)
    if not reagent or not reagent.anyOf then
        return nil, nil
    end

    if Auctionator and Auctionator.API and Auctionator.API.v1
       and Auctionator.API.v1.GetAuctionPriceByItemID
    then
        local bestItemID = nil
        local bestPrice = nil

        for index, opt in ipairs(reagent.anyOf) do
            local itemID = opt and opt.itemID
            if itemID then
                local price = Auctionator.API.v1.GetAuctionPriceByItemID(
                    "AhUI ProfessionGuide",
                    itemID
                )

                if price and price > 0 then
                    if not bestPrice or price < bestPrice then
                        bestPrice = price
                        bestItemID = itemID
                    end
                end
            end
        end

        if bestItemID then
            return bestItemID, bestPrice
        end
    end

    local first = reagent.anyOf[1]
    return first and first.itemID or nil, nil
end

function UI:SetScale(delta)
    local cfg = GetPGConfig()
    local cur = cfg.scale or DEFAULT_SCALE

    local nextScale = cur + delta
    nextScale = math.max(SCALE_MIN, math.min(SCALE_MAX, nextScale))

    cfg.scale = nextScale

    if self.frame then
        self.frame:SetScale(nextScale)
    end
end

function UI:ResetScale()
    local cfg = GetPGConfig()
    cfg.scale = DEFAULT_SCALE

    if self.frame then
        self.frame:SetScale(DEFAULT_SCALE)
    end
end

function UI:Create()
    if self.frame then return self.frame end

    local f = CreateFrame("Frame", "AhUIProfessionGuide", UIParent, "BackdropTemplate")
    f:SetSize(500, 210)
	f:ClearAllPoints()
	f:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 400, -200)
	f:SetWidth(500)
    f:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
    })
    f:SetBackdropColor(0, 0, 0, 0.92)
    f:SetBackdropBorderColor(1, 1, 1, 1)

    table.insert(UISpecialFrames, f:GetName())

    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", function(self)
		self:StopMovingOrSizing()

		local left, top = self:GetLeft(), self:GetTop()

		self:ClearAllPoints()
		self:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", left, top)
		self:SetWidth(self:GetWidth())
	end)
	
	f:EnableMouseWheel(true)

	f:SetScript("OnMouseWheel", function(self, delta)
		if not IsAltKeyDown() then return end

		if delta > 0 then
			UI:SetScale(SCALE_STEP)
		else
			UI:SetScale(-SCALE_STEP)
		end
	end)

	f:SetScript("OnMouseUp", function(self, button)
		if button == "MiddleButton" and IsAltKeyDown() then
			UI:ResetScale()
		end
	end)

    f.title = f:CreateFontString(nil, "OVERLAY")
    f.title:SetFont(FONT, 14, "OUTLINE")
    f.title:SetPoint("TOPLEFT", 14, -12)
    f.title:SetText(T("PG_WINDOW_TITLE", "Profession Guides."))
	f.profIconHolder = CreateFrame("Frame", nil, f, "BackdropTemplate")
	f.profIconHolder:SetSize(26, 26)
	f.profIconHolder:SetPoint("LEFT", f.title, "RIGHT", 8, 0)
	f.profIconHolder:SetFrameLevel(f:GetFrameLevel() + 5)

	f.profIcon = f.profIconHolder:CreateTexture(nil, "ARTWORK", nil, 5)
	f.profIcon:SetAllPoints()

	CreateIconBorder(f.profIconHolder)

	f.profIconHolder:Hide()

	local function ShowCursorStyledTooltip(lines, offsetX, offsetY)
		if not Tooltip then return end
		Tooltip:Clear()

		for _, line in ipairs(lines or {}) do
			if line and line.spacer then
				Tooltip:AddLine(" ")
			elseif line then
				Tooltip:AddLine(
					line.text or "",
					line.size,
					line.r,
					line.g,
					line.b
				)
			end
		end

		Tooltip:ShowAtCursor(offsetX or 16, offsetY or 16)
	end



	f.profIconHolder:SetScript("OnEnter", function()
		ShowCursorStyledTooltip({
			{ text = T("PG_TOOLTIP_OPEN_PROFESSION", "Open profession"), size = 14, r = 1, g = 1, b = 1 },
		}, 16, 16)
	end)
	f.profHint = f:CreateFontString(nil, "OVERLAY")
	f.profHint:SetFont(FONT, 11)
	f.profHint:SetPoint("LEFT", f.profIconHolder, "RIGHT", 6, 0)
	f.profHint:SetText(T("PG_OPEN_PROF_HINT", "|cffaaaaaa<< open\nprofession|r"))
	f.profHint:Hide()

	f.profIconHolder:SetScript("OnLeave", function()
		if Tooltip then Tooltip:Clear() else GameTooltip:Hide() end
	end)

	f.profIconHolder:SetScript("OnMouseDown", function(_, button)
		if button ~= "LeftButton" then return end

		f.profIcon:SetPoint("CENTER", 1, -1)

		local skillLineID = G:GetSelectedSkillLine()
		if not skillLineID then return end

		if not C_TradeSkillUI
		   or not C_TradeSkillUI.OpenTradeSkill
		then
			return
		end

		local parentID = G:GetParentProfessionID(skillLineID)
		if not parentID then return end

		if ProfessionsFrame and ProfessionsFrame:IsShown()
		   and lastOpenedSkillLine == parentID
		then
			HideUIPanel(ProfessionsFrame)
			lastOpenedSkillLine = nil
			return
		end

		ShowUIPanel(ProfessionsFrame)
		C_TradeSkillUI.OpenTradeSkill(parentID)

		lastOpenedSkillLine = parentID
	end)

	f.profIconHolder:SetScript("OnMouseUp", function(_, button)
		if button == "LeftButton" then
			f.profIcon:SetPoint("CENTER", 0, 0)
		end
	end)

    f.close = CreateFrame("Button", nil, f)
    f.close:SetSize(16, 16)
    f.close:SetPoint("TOPRIGHT", -10, -10)

    local c = f.close:CreateFontString(nil, "OVERLAY")
    c:SetFont(FONT, 25)
    c:SetPoint("CENTER")
    c:SetText("×")
    c:SetTextColor(0.7, 0.7, 0.7)

    f.close:SetScript("OnEnter", function() c:SetTextColor(1, 0.4, 0.4) end)
    f.close:SetScript("OnLeave", function() c:SetTextColor(0.7, 0.7, 0.7) end)
    f.close:SetScript("OnClick", function() f:Hide() end)
	f.infoBtn = CreateFrame("Button", nil, f)
	f.infoBtn:SetSize(14, 14)
	f.infoBtn:SetPoint("TOPRIGHT", f.close, "BOTTOMRIGHT", 0, -4)
	f.infoBtn:SetFrameLevel(f.close:GetFrameLevel() + 5)
	f.infoBtn.icon = f.infoBtn:CreateTexture(nil, "OVERLAY")
	f.infoBtn.icon:SetSize(25, 25)
	f.infoBtn.icon:SetPoint("CENTER")
	f.infoBtn.icon:SetDrawLayer("OVERLAY", 7)
	f.infoBtn.icon:SetTexture("Interface\\COMMON\\help-i")
	f.infoBtn.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
	f.infoBtn.icon:SetAlpha(0.85)
	f.infoBtn:SetScript("OnEnter", function(self)
	    if self.icon then
			self.icon:SetAlpha(1)
		end

		ShowCursorStyledTooltip({
			{ text = T("PG_WINDOW_TITLE", "Profession Guides."), size = 14, r = 1, g = 1, b = 1 },
			{ spacer = true },
			{ text = T("PG_HELP_1", "• Select a profession available to the character"), size = 13, r = 0.9, g = 0.9, b = 0.9 },
			{ text = T("PG_HELP_2", "• Select the profession expansion"), size = 13, r = 0.9, g = 0.9, b = 0.9 },
			{ text = T("PG_HELP_3", "• Open the profession to enable crafting buttons"), size = 13, r = 0.9, g = 0.9, b = 0.9 },
			{ text = T("PG_HELP_4", "• Use the Previous / Next buttons"), size = 13, r = 0.9, g = 0.9, b = 0.9 },
			{ text = T("PG_HELP_5", "     to move through guide steps"), size = 12, r = 0.75, g = 0.75, b = 0.75 },
			{ text = T("PG_HELP_6", "• Create crafts 1 item"), size = 13, r = 0.9, g = 0.9, b = 0.9 },
			{ text = T("PG_HELP_7", "• Create All crafts the maximum possible amount"), size = 13, r = 0.9, g = 0.9, b = 0.9 },
			{ text = T("PG_HELP_8", "• Left-click the trainer name to place a waypoint"), size = 13, r = 0.9, g = 0.9, b = 0.9 },
			{ text = T("PG_HELP_9", "     do not forget to learn new recipes"), size = 12, r = 0.75, g = 0.75, b = 0.75 },
			{ spacer = true },
			{ text = T("PG_HELP_10", "Alt + wheel - scale"), size = 12, r = 0.75, g = 0.75, b = 0.75 },
			{ text = T("PG_HELP_11", "Alt + middle click - reset"), size = 12, r = 0.75, g = 0.75, b = 0.75 },
		}, 16, 16)
	end)
	f.infoBtn:SetScript("OnLeave", function(self)
	    if self.icon then
			self.icon:SetAlpha(0.85)
		end
		if Tooltip then
			Tooltip:Clear()
		else
			GameTooltip:Hide()
		end
	end)

	f.profBtn = CreateFrame("Button", nil, f)
	f.profBtn:SetFrameLevel(f:GetFrameLevel() + 20)
	f.profBtn:SetSize(160, 22)
	f.profBtn:SetPoint("TOPRIGHT", -40, -12)

	local bgp = f.profBtn:CreateTexture(nil, "BACKGROUND")
	bgp:SetAllPoints()
	bgp:SetColorTexture(1, 1, 1, 0.08)

	f.profBtn.text = f.profBtn:CreateFontString(nil, "OVERLAY")
	f.profBtn.text:SetFont(FONT, 12)
	f.profBtn.text:SetPoint("CENTER")
	f.profBtn.text:SetText(T("PG_SELECT_PROFESSION", "Select profession"))

    f.epochBtn = CreateFrame("Button", nil, f)
	f.epochBtn:SetFrameLevel(f:GetFrameLevel() + 20)
    f.epochBtn:SetSize(160, 22)
    f.epochBtn:SetPoint("TOPRIGHT", -40, -40)

    local bgEpoch = f.epochBtn:CreateTexture(nil, "BACKGROUND")
    bgEpoch:SetAllPoints()
    bgEpoch:SetColorTexture(1, 1, 1, 0.08)

    f.epochBtn.text = f.epochBtn:CreateFontString(nil, "OVERLAY")
    f.epochBtn.text:SetFont(FONT, 12)
    f.epochBtn.text:SetPoint("CENTER")
    f.epochBtn.text:SetText(T("PG_SELECT_EPOCH", "Select expansion"))
	f.buyBtn = CreateFrame("Button", nil, f)
	f.buyBtn:SetFrameLevel(f:GetFrameLevel() + 20)
	f.buyBtn:SetSize(160, 22)
	f.buyBtn:SetPoint("TOPRIGHT", -40, -68)
	f.buyBtn:Hide()

	local bgBuy = f.buyBtn:CreateTexture(nil, "BACKGROUND")
	bgBuy:SetAllPoints()
	bgBuy:SetColorTexture(0.1, 0.4, 0.9, 0.25)

	local glow = CreateFrame("Frame", nil, f.buyBtn, "BackdropTemplate")
	glow:SetPoint("TOPLEFT", f.buyBtn, "TOPLEFT", -3, 3)
	glow:SetPoint("BOTTOMRIGHT", f.buyBtn, "BOTTOMRIGHT", 3, -3)
	glow:SetFrameLevel(f.buyBtn:GetFrameLevel() - 1)
	glow:SetBackdrop({
		edgeFile = "Interface\\Buttons\\WHITE8x8",
		edgeSize = 2,
	})
	glow:SetBackdropBorderColor(0.3, 0.7, 1, 0.9)

	local ag = glow:CreateAnimationGroup()
	ag:SetLooping("BOUNCE")
	local anim = ag:CreateAnimation("Alpha")
	anim:SetFromAlpha(0.4)
	anim:SetToAlpha(1.0)
	anim:SetDuration(1.2)
	ag:Play()

	f.buyBtn.text = f.buyBtn:CreateFontString(nil, "OVERLAY")
	f.buyBtn.text:SetFont(FONT, 12)
	f.buyBtn.text:SetPoint("CENTER")
	f.buyBtn.text:SetText(T("PG_CREATE_SHOPPING_LIST", "Create shopping list"))
	f.buyBtn:SetScript("OnClick", function()
		local skillLineID = G:GetSelectedSkillLine()
		if not skillLineID then return end

		local mats = G:BuildShoppingList(skillLineID)
		if not mats or not next(mats) then
			print(T("PG_ERR_NOTHING_TO_BUY", "AhUI: nothing to buy"))
			return
		end

		local S = PT.ProfessionGuides_Shopping
		if not S or not S:IsAvailable() then
			print(T("PG_ERR_AUCTIONATOR_UNAVAILABLE", "AhUI: Auctionator is unavailable"))
			return
		end

		local ok, reason = S:CreateShoppingSession(mats, skillLineID)
		if not ok then
			print(T("PG_ERR_AUCTION", "AhUI: auction error:"), reason)
		end

	end)
	f.buyBtn:SetScript("OnEnter", function(self)
		ShowCursorStyledTooltip({
			{ text = T("PG_CREATE_SHOPPING_LIST", "Create shopping list"), size = 14, r = 1, g = 1, b = 1 },
			{ spacer = true },
			{ text = T("PG_SHOPPING_TOOLTIP_1", "Creates a shopping list in Auctionator."), size = 13, r = 0.9, g = 0.9, b = 0.9 },
			{ text = T("PG_SHOPPING_TOOLTIP_2", "Open the Shopping tab to use the list."), size = 13, r = 0.9, g = 0.9, b = 0.9 },
		}, 16, 16)
	end)

	f.buyBtn:SetScript("OnLeave", function()
		if Tooltip then
			Tooltip:Clear()
		else
			GameTooltip:Hide()
		end
	end)

	f.nextStepBtn = CreateFrame("Button", nil, f)
	f.nextStepBtn:SetFrameLevel(f:GetFrameLevel() + 20)
	f.nextStepBtn:SetSize(100, 20)
	f.nextStepBtn:SetPoint("TOPRIGHT", -40, -96)
	f.nextStepBtn:Hide()

	local bgNext = f.nextStepBtn:CreateTexture(nil, "BACKGROUND")
	bgNext:SetAllPoints()
	bgNext:SetColorTexture(1, 1, 1, 0.06)

	f.nextStepBtn.text = f.nextStepBtn:CreateFontString(nil, "OVERLAY")
	f.nextStepBtn.text:SetFont(FONT, 11)
	f.nextStepBtn.text:SetPoint("CENTER")
	f.nextStepBtn.text:SetText(T("PG_NEXT", "Next >"))

	f.prevStepBtn = CreateFrame("Button", nil, f)
	f.prevStepBtn:SetFrameLevel(f:GetFrameLevel() + 20)
	f.prevStepBtn:SetSize(100, 20)
	f.prevStepBtn:SetPoint("TOPLEFT", f.nextStepBtn, "BOTTOMLEFT", -60, -6)
	f.prevStepBtn:Hide()

	local bgPrev = f.prevStepBtn:CreateTexture(nil, "BACKGROUND")
	bgPrev:SetAllPoints()
	bgPrev:SetColorTexture(1, 1, 1, 0.06)

	f.prevStepBtn.text = f.prevStepBtn:CreateFontString(nil, "OVERLAY")
	f.prevStepBtn.text:SetFont(FONT, 11)
	f.prevStepBtn.text:SetPoint("CENTER")
	f.prevStepBtn.text:SetText(T("PG_PREV", "< Previous"))

	f.prevStepBtn:SetScript("OnClick", function()
		G:StepPrev()
		State:Save()
		UI:Refresh()
	end)

	f.nextStepBtn:SetScript("OnClick", function()
		G:StepNext()
		State:Save()
		UI:Refresh()
	end)

	f.stepIndicator = f:CreateFontString(nil, "OVERLAY")
	f.stepIndicator:SetFont(FONT, 11)
	f.stepIndicator:SetPoint("LEFT", f.prevStepBtn, "RIGHT", 8, 0)
	f.stepIndicator:SetText("")
	f.stepIndicator:Hide()
	f.resetStepBtn = CreateFrame("Button", nil, f)
	f.resetStepBtn:SetFrameLevel(f:GetFrameLevel() + 20)
	f.resetStepBtn:SetSize(160, 20)
	f.resetStepBtn:SetPoint("TOPRIGHT", -40, -148)
	f.resetStepBtn:Hide()

	local bgReset = f.resetStepBtn:CreateTexture(nil, "BACKGROUND")
	bgReset:SetAllPoints()
	bgReset:SetColorTexture(1, 1, 1, 0.06)

	f.resetStepBtn.text = f.resetStepBtn:CreateFontString(nil, "OVERLAY")
	f.resetStepBtn.text:SetFont(FONT, 11)
	f.resetStepBtn.text:SetPoint("CENTER")
	f.resetStepBtn.text:SetText(T("PG_RESET_TO_CURRENT", "Back to current step"))
	f.resetStepBtn:SetScript("OnClick", function()
		G:ResetManualStep()
		State:Save()
		UI:Refresh()
	end)
	f.content = CreateFrame("Frame", nil, f)
	f.content:SetPoint("TOPLEFT", f.title, "BOTTOMLEFT", 0, -16)
	f.content:SetPoint("TOPRIGHT", f, "TOPRIGHT", -16, -48)
	f.content:SetFrameLevel(f:GetFrameLevel() + 1)
	f.content:SetHeight(1)
	f.content:SetHeight(1)
	f.textLines = {}
	f.trainersHeader = f.content:CreateFontString(nil, "OVERLAY")
	f.trainersHeader:SetFont(FONT, 12)
	f.trainersHeader:SetText(T("PG_TRAINERS", "Trainers:"))
	f.trainersHeader:Hide()

	f.trainerButtons = f.trainerButtons or {}

	f.footer = CreateFrame("Frame", nil, f)
	f.footer:SetPoint("TOPLEFT", f.content, "BOTTOMLEFT", 0, 6)
	f.footer:SetPoint("TOPRIGHT", f.content, "BOTTOMRIGHT", 0, 6)
	f.footer:SetHeight(72)

	f.craftAllBtn = CreateFrame("Button", nil, f)
	f.craftAllBtn:SetSize(90, 20)
	f.craftAllBtn:Hide()
	f.craftAllBtn:SetPoint("TOPRIGHT", f.footer, "TOPRIGHT", -24, 0)

	local bg2 = f.craftAllBtn:CreateTexture(nil, "BACKGROUND")
	bg2:SetAllPoints()
	bg2:SetColorTexture(1, 1, 1, 0.06)

	f.craftAllBtn.text = f.craftAllBtn:CreateFontString(nil, "OVERLAY")
	f.craftAllBtn.text:SetFont(FONT, 12)
	f.craftAllBtn.text:SetPoint("CENTER")
	f.craftAllBtn.text:SetText(T("PG_CREATE_ALL", "Create All"))
	f.craftBtn = CreateFrame("Button", nil, f)
	f.craftBtn:SetSize(90, 20)
	f.craftBtn:Hide()
	f.craftBtn:SetPoint("BOTTOMRIGHT", f.craftAllBtn, "TOPRIGHT", 0, 6)

	local bg1 = f.craftBtn:CreateTexture(nil, "BACKGROUND")
	bg1:SetAllPoints()
	bg1:SetColorTexture(1, 1, 1, 0.08)

	f.craftBtn.text = f.craftBtn:CreateFontString(nil, "OVERLAY")
	f.craftBtn.text:SetFont(FONT, 12)
	f.craftBtn.text:SetPoint("CENTER")
	f.craftBtn.text:SetText(T("PG_CREATE", "Create"))

	ApplyHoverHighlight(f.profBtn, bgp, f.profBtn.text, { normalAlpha = 0.08, hoverAlpha = 0.16 })
	ApplyHoverHighlight(f.epochBtn, bgEpoch, f.epochBtn.text, { normalAlpha = 0.08, hoverAlpha = 0.16 })
	ApplyHoverHighlight(f.buyBtn, bgBuy, f.buyBtn.text, { normalAlpha = 0.25, hoverAlpha = 0.38 })
	ApplyHoverHighlight(f.nextStepBtn, bgNext, f.nextStepBtn.text, { normalAlpha = 0.06, hoverAlpha = 0.14 })
	ApplyHoverHighlight(f.prevStepBtn, bgPrev, f.prevStepBtn.text, { normalAlpha = 0.06, hoverAlpha = 0.14 })
	ApplyHoverHighlight(f.resetStepBtn, bgReset, f.resetStepBtn.text, { normalAlpha = 0.06, hoverAlpha = 0.14 })
	ApplyHoverHighlight(f.craftBtn, bg1, f.craftBtn.text, { normalAlpha = 0.08, hoverAlpha = 0.16 })
	ApplyHoverHighlight(f.craftAllBtn, bg2, f.craftAllBtn.text, { normalAlpha = 0.06, hoverAlpha = 0.14 })


	f.craftBtn:SetScript("OnClick", function()
		local skillLineID = G:GetSelectedSkillLine()
		if not skillLineID then return end

		local step = G:GetCurrentStepBySkillLine(skillLineID)
		if not step or not step.recipes or not step.recipes[1] then
			print(T("PG_ERR_NO_RECIPE_FOR_CRAFT", "AhUI: no recipe available for crafting"))
			return
		end

		local recipe = step.recipes[1]
		CraftRecipe(recipe.spellID, 1)
	end)

	f.enchantTargetSlot = CreateFrame("Button", nil, f)
	f.enchantTargetSlot:SetSize(36, 36)
	f.enchantTargetSlot:SetPoint("RIGHT", f.craftBtn, "LEFT", -8, -13)
	f.enchantTargetSlot:Hide()

	f.enchantTargetSlot.bg = f.enchantTargetSlot:CreateTexture(nil, "BACKGROUND")
	f.enchantTargetSlot.bg:SetAllPoints()
	f.enchantTargetSlot.bg:SetColorTexture(1, 1, 1, 0.08)

	f.enchantTargetSlot.icon = f.enchantTargetSlot:CreateTexture(nil, "ARTWORK")
	f.enchantTargetSlot.icon:SetAllPoints()
	f.enchantTargetSlot.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

	f.enchantTargetSlot.label = f.enchantTargetSlot:CreateFontString(nil, "OVERLAY")
	f.enchantTargetSlot.label:SetFont(FONT, 9)
	f.enchantTargetSlot.label:SetPoint("TOP", f.enchantTargetSlot, "BOTTOM", 0, -2)
	f.enchantTargetSlot.label:SetText(T("PG_ENCHANT_TARGET", "Enchant target"))
	f.enchantTargetSlot.label:SetTextColor(0.7, 0.7, 0.7)

	f.enchantTargetSlot:SetScript("OnClick", function(self)
		local cursorType, _, itemLink = GetCursorInfo()
		if cursorType == "item" then
			local targetLoc = nil
			for bag = 0, NUM_BAG_SLOTS do
				local slots = C_Container.GetContainerNumSlots(bag)
				if slots then
					for slot = 1, slots do
						local link = C_Container.GetContainerItemLink(bag, slot)
						if link == itemLink then
							targetLoc = ItemLocation:CreateFromBagAndSlot(bag, slot)
							break
						end
					end
				end
				if targetLoc then break end
			end
			if targetLoc then
				enchantTargetLocation = targetLoc
				local icon = C_Item.GetItemIconByID(C_Container.GetContainerItemID(
					targetLoc:GetBagAndSlot()))
				if icon then
					self.icon:SetTexture(icon)
				end
				ClearCursor()
			end
		elseif enchantTargetLocation then

			enchantTargetLocation = nil
			self.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
		end
	end)

	f.enchantTargetSlot:SetScript("OnReceiveDrag", function(self)
		local cursorType, _, itemLink = GetCursorInfo()
		if cursorType == "item" then
			local targetLoc = nil
			for bag = 0, NUM_BAG_SLOTS do
				local slots = C_Container.GetContainerNumSlots(bag)
				if slots then
					for slot = 1, slots do
						local link = C_Container.GetContainerItemLink(bag, slot)
						if link == itemLink then
							targetLoc = ItemLocation:CreateFromBagAndSlot(bag, slot)
							break
						end
					end
				end
				if targetLoc then break end
			end
			if targetLoc then
				enchantTargetLocation = targetLoc
				local icon = C_Item.GetItemIconByID(C_Container.GetContainerItemID(
					targetLoc:GetBagAndSlot()))
				if icon then
					self.icon:SetTexture(icon)
				end
				ClearCursor()
			end
		end
	end)

	f.enchantTargetSlot:SetScript("OnEnter", function(self)
		if enchantTargetLocation then
			local bag, slot = enchantTargetLocation:GetBagAndSlot()
			ShowGameTooltipAtCursor(function(tt)
				tt:SetBagItem(bag, slot)
			end, 16, 16)
		else
			ShowGameTooltipAtCursor(function(tt)
				tt:SetText(T("PG_DRAG_ITEM_FOR_ENCHANT", "Drag an item here for enchanting"), nil, nil, nil, nil, true)
			end, 16, 16)
		end
	end)

	f.enchantTargetSlot:SetScript("OnLeave", function()
		GameTooltip:Hide()
	end)

	f.enchantTargetSlot.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
	f.enchantTargetSlot.icon:SetDesaturated(true)

	f.craftAllBtn:SetScript("OnClick", function()
		local skillLineID = G:GetSelectedSkillLine()
		if not skillLineID then return end

		local step = G:GetCurrentStepBySkillLine(skillLineID)
		if not step or not step.recipes or not step.recipes[1] then
			print(T("PG_ERR_NO_RECIPE_FOR_CRAFT", "AhUI: no recipe available for crafting"))
			return
		end

		local recipe = step.recipes[1]

		local need = G:GetRemainingCraftsForStep(skillLineID, step)
		if need <= 0 then
			print(T("PG_ERR_NO_MORE_CRAFTS_NEEDED", "AhUI: no more crafts needed"))
			return
		end

		local recipeReagents, reagentState = G:GetResolvedRecipeReagents(skillLineID, recipe)
		if reagentState == "loading" and (not recipeReagents or #recipeReagents == 0) then
			print(T("PG_ERR_REAGENTS_LOADING", "AhUI: recipe reagents are still loading"))
			return
		end

		local byMaterials = G:GetMaxCraftsByMaterials(recipe, skillLineID)
		if byMaterials <= 0 then
			print(T("PG_ERR_NOT_ENOUGH_MATERIALS", "AhUI: not enough materials"))
			return
		end

		local craftCount = math.min(need, byMaterials)
		if craftCount <= 0 then
			print(T("PG_ERR_NOT_ENOUGH_MATERIALS", "AhUI: not enough materials"))
			return
		end

		CraftRecipe(recipe.spellID, craftCount)

	end)
	
    f:Hide()
    self.frame = f
	State:Restore()

	if State.materialsExpanded == nil then
		State.materialsExpanded = false
	end

    UI:Refresh()

    return f
end

local _menuOverlay = nil
local _activeMenu  = nil  

local function GetMenuOverlay()
    if _menuOverlay then return _menuOverlay end

    _menuOverlay = CreateFrame("Frame", nil, UIParent)
    _menuOverlay:SetAllPoints(UIParent)
    _menuOverlay:SetFrameStrata("FULLSCREEN_DIALOG")
    _menuOverlay:EnableMouse(true)
    _menuOverlay:Hide()

    _menuOverlay:SetScript("OnMouseDown", function()
        if _activeMenu then
            _activeMenu:Hide()
            _activeMenu = nil
        end
        _menuOverlay:Hide()
    end)

    return _menuOverlay
end

local function OpenMenu(menu, btn)
    local overlay = GetMenuOverlay()

    if _activeMenu and _activeMenu ~= menu then
        _activeMenu:Hide()
    end

    menu:SetParent(overlay)
    menu:ClearAllPoints()
    menu:SetPoint("TOPRIGHT", btn, "BOTTOMRIGHT", 0, -2)

    if UI.frame then
        menu:SetScale(UI.frame:GetScale())
    end

    _activeMenu = menu
    overlay:Show()
    menu:Show()
end

local function CloseMenu(menu)
    if menu then menu:Hide() end
    if _menuOverlay then _menuOverlay:Hide() end
    _activeMenu = nil
end

local _profMenu = nil
local _profMenuItems = {}  
local function BuildProfMenu()
    if not Epochs.professionsOrder then return end

    local overlay = GetMenuOverlay()
    local itemHeight = 18

    local m = CreateFrame("Frame", nil, overlay, "BackdropTemplate")
    m:SetSize(160, #Epochs.professionsOrder * itemHeight + 12)
    m:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
    })
    m:SetBackdropColor(0, 0, 0, 0.95)
    m:SetBackdropBorderColor(1, 1, 1, 0.15)
    m:EnableMouse(true)
    m:SetScript("OnHide", function() CloseMenu(nil) end)
    m:Hide()

    local y = -6
    for _, prof in ipairs(Epochs.professionsOrder) do
        local b = CreateFrame("Button", nil, m)
        b:SetPoint("TOPLEFT", 8, y)
        b:SetSize(144, itemHeight)

        local fs = b:CreateFontString(nil, "OVERLAY")
        fs:SetFont(FONT, 12)
        fs:SetPoint("LEFT")

        local displayName = (PT.L and PT.L[prof]) or prof
        fs:SetText(displayName)

        b._prof        = prof
        b._displayName = displayName

        b:SetScript("OnClick", function(self)
            G:SetProfession(self._prof)
            State:Save()

            if UI.frame and UI.frame.profBtn then
                UI.frame.profBtn.text:SetText(self._displayName)
            end
            if UI.frame and UI.frame.epochBtn then
                UI.frame.epochBtn.text:SetText(T("PG_SELECT_EPOCH", "Select expansion"))
            end

            CloseMenu(_profMenu)
            UI:Refresh()
        end)

        table.insert(_profMenuItems, { btn = b, label = fs })
        y = y - itemHeight
    end

    return m
end

local function ShowProfessionMenu(btn)
    if not Epochs.professionsOrder then return end

    if not _profMenu then
        _profMenu = BuildProfMenu()
    end
    if not _profMenu then return end

    OpenMenu(_profMenu, btn)
end

local _epochMenu      = nil
local _epochMenuBtns  = {}  

local function GetOrCreateEpochMenu()
    if _epochMenu then return _epochMenu end

    local overlay = GetMenuOverlay()
    local m = CreateFrame("Frame", nil, overlay, "BackdropTemplate")
    m:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
    })
    m:SetBackdropColor(0, 0, 0, 0.95)
    m:SetBackdropBorderColor(1, 1, 1, 0.15)
    m:EnableMouse(true)
    m:SetScript("OnHide", function() CloseMenu(nil) end)
    m:Hide()

    _epochMenu = m
    return m
end

local function ShowEpochMenu(btn)
    local prof = G:GetSelectedProfession()
    if not prof then
        print(T("PG_ERR_SELECT_PROFESSION", "AhUI: select a profession"))
        return
    end

    local profEpochs = Epochs[prof]
    if not profEpochs then
        print("AhUI: no epochs for profession", prof)
        return
    end

    local order      = profEpochs.order
    local data       = profEpochs.data
    local itemHeight = 18
    local m          = GetOrCreateEpochMenu()

    for _, b in ipairs(_epochMenuBtns) do
        b:Hide()
    end

    local count = 0
    for _, epochSkillLineID in ipairs(order) do
        local info = data[epochSkillLineID]
        if info then
            count = count + 1
            local b = _epochMenuBtns[count]

            if not b then
                b = CreateFrame("Button", nil, m)
                b:SetSize(144, itemHeight)

                local fs = b:CreateFontString(nil, "OVERLAY")
                fs:SetFont(FONT, 12)
                fs:SetPoint("LEFT")
                b._label = fs

                b:SetScript("OnClick", function(self)
                    G:SetSkillLine(self._epochID)
                    State:Save()

                    if UI.frame and UI.frame.epochBtn then
                        UI.frame.epochBtn.text:SetText(self._epochName)
                    end

                    CloseMenu(_epochMenu)
                    UpdateBuyButtonVisibility()
                    UI:Refresh()
                end)

                _epochMenuBtns[count] = b
            end

            b._epochID   = epochSkillLineID
            local epochDisplayName = (info.nameKey and T(info.nameKey)) or info.name
            b._epochName = epochDisplayName
            b._label:SetText(epochDisplayName)
            b:ClearAllPoints()
            b:SetPoint("TOPLEFT", 8, -6 - (count - 1) * itemHeight)
            b:Show()
        end
    end

    m:SetSize(160, count * itemHeight + 12)
    OpenMenu(m, btn)
end

local function GetTextLine(f, index)
    local line = f.textLines[index]

    if not line then
        line = CreateFrame("Button", nil, f.content)
        line:SetHeight(16)
        line:SetWidth(380)
        line:EnableMouse(true)

        line.text = line:CreateFontString(nil, "OVERLAY")
        line.text:SetFont(FONT, 12)
        line.text:SetPoint("LEFT", 0, 0)
        line.text:SetJustifyH("LEFT")

        line:SetScript("OnEnter", function(self)
            if self.itemID then
                ShowGameTooltipAtCursor(function(tt)
                    tt:SetItemByID(self.itemID)
                end, 16, 16)
            end
        end)

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

        line:SetScript("OnClick", function(self)
            if self.itemLink then
                ChatEdit_InsertLink(self.itemLink)
            end
        end)

        f.textLines[index] = line
    end

    return line
end

local function BuildNoteRefs(step)
    if not step or not step.noteRefs then return nil end

    local out = {}

    for _, ref in ipairs(step.noteRefs) do
        if ref.type == "spell" and ref.spellID then
            local name

            if C_Spell and C_Spell.GetSpellInfo then
                local info = C_Spell.GetSpellInfo(ref.spellID)
                name = info and info.name
            elseif GetSpellInfo then
                name = GetSpellInfo(ref.spellID)
            end

            table.insert(out, "• " .. (name or ("spell:" .. ref.spellID)))

        elseif ref.type == "item" and ref.itemID then
            local name = GetCachedItemName(ref.itemID)
            table.insert(out, "• " .. (name or ("item:" .. ref.itemID)))
        end
    end

    if #out == 0 then return nil end
    return out
end

local function GetFirstLearnedRecipe(step)
	if not step or not step.recipes then return nil end

	for _, r in ipairs(step.recipes) do
		if r.spellID and G:IsRecipeLearned(r.spellID) then
			return r
		end
	end

	return nil
end

function UI:Refresh()
    local f = self.frame
    local skillLineID = G:GetSelectedSkillLine()

for _, l in ipairs(f.textLines) do
    l:Hide()
    if l.icon then l.icon:Hide() end
end

if f.trainersHeader then
    f.trainersHeader:Hide()
end

if f.trainerButtons then
    for _, b in ipairs(f.trainerButtons) do
        b:Hide()
    end
end

local ctx = { y = 0, i = 1 }
local skipDefaultTrainers = false

local function AddLine(text, color)
    local line = GetTextLine(f, ctx.i)

    if line.icon then
        line.icon:Hide()
    end

    line:SetWidth(TEXT_SAFE_WIDTH)

    line.text:ClearAllPoints()
    line.text:SetPoint("LEFT", 0, 0)
    line.text:SetWidth(TEXT_SAFE_WIDTH)
    line.text:SetWordWrap(true)

    line:ClearAllPoints()
    line:SetPoint("TOPLEFT", f.content, "TOPLEFT", 0, -ctx.y)

    if color then
        text = color .. text .. "|r"
    end

    line.text:SetText(text)

    line.itemID = nil
    line.itemLink = nil
    line:Show()

    local h = line.text:GetStringHeight()
    line:SetHeight(h)

    ctx.y = ctx.y + h + LINE_SPACING
    ctx.i = ctx.i + 1
end

ctx.y = ctx.y + 6

f.profIconHolder:Hide()
if f.profHint then f.profHint:Hide() end

if skillLineID and G.GetProfessionIcon then
    local icon = G:GetProfessionIcon(skillLineID)
    if icon then
        f.profIcon:SetTexture(icon)
        f.profIconHolder:Show()
        if f.profHint then f.profHint:Show() end
    end
end

if not skillLineID then

	AddLine(T("PG_EMPTY_1", "|cff66ccff• Select a profession available to the character.|r"))
	AddLine(T("PG_EMPTY_2", "|cff4da6ff• Select the profession expansion.|r"))
	AddLine(T("PG_EMPTY_3", "|cff66ccff• Open the profession to enable crafting buttons.|r"))
	AddLine(T("PG_EMPTY_4", "|cff66ff66• Use the |cffffcc00Previous / Next|r buttons"))
	AddLine(T("PG_EMPTY_5", "|cff66ff66to move through guide steps.|r"))
	AddLine(T("PG_EMPTY_6", "|cffff9900• Create crafts 1 item.|r"))
	AddLine(T("PG_EMPTY_7", "|cffff9900• Create All crafts the maximum possible amount.|r"))
	AddLine(T("PG_EMPTY_8", "|cff66ff66• Left-click the trainer button to place a waypoint.|r"))
	AddLine(T("PG_EMPTY_9", "|cffffdd55  Do not forget to learn new recipes from the guide.|r"))
	AddLine("")
	AddLine(T("PG_EMPTY_10", "|cff66ff66Recommendation:|r\nafter Classic, you can learn Cataclysm right away — the trainer is the same.\n"))
	AddLine(T("PG_EMPTY_11", "|cffffcc00Mail crafted armor and weapons to your enchanter to save reagents.|r"))
	AddLine("")
	AddLine(T("PG_EMPTY_12", "|cff888888Alt + mouse wheel - window scale|r"))
	AddLine(T("PG_EMPTY_13", "|cff888888Alt + middle click - reset window scale|r"))
    if f.craftBtn then f.craftBtn:Hide() end
    if f.craftAllBtn then f.craftAllBtn:Hide() end
    if f.enchantTargetSlot then f.enchantTargetSlot:Hide() end

    UpdateBuyButtonVisibility()

    local contentHeight = ctx.y
    local footerHeight = 0
    if f.footer and f.footer:IsShown() then
        footerHeight = f.footer:GetHeight() + 6
    end

    f.content:SetHeight(contentHeight)

    local totalHeight =
        FRAME_PADDING_TOP
        + contentHeight
        + footerHeight
        + FRAME_PADDING_BOTTOM
        + 32

    local left = f:GetLeft()
    local top  = f:GetTop()

    f:SetHeight(math.max(totalHeight, FRAME_MIN_HEIGHT))

    if left and top then
        f:ClearAllPoints()
        f:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", left, top)
    end

    return
end

    local step, skill, guide =
        G:GetCurrentStepBySkillLine(skillLineID)

	local autoIdx = G:GetAutoStepIndex(skillLineID)

	if G.state.manualStepIndex
	   and autoIdx
	   and G.state.manualStepIndex == autoIdx
	then
		G.state.manualStepIndex = nil
		skill = G:GetEffectiveSkillLevel(skillLineID)
	end
	
	local recipeVendor =
		step and G:GetRecipeVendorForStep(skillLineID, step)

	if not guide then
		guide = G:GetGuideBySkillLine(skillLineID)
	end
	local recipe = GetFirstLearnedRecipe(step)
	local recipeLearned = recipe ~= nil

	if G:IsGuideCompleted(skillLineID) then
		AddLine(T("PG_GUIDE_COMPLETE", "Guide complete!"), "|cff00ff00")
		AddLine(T("PG_GUIDE_COMPLETE_DESC", "You have reached the maximum skill for this expansion."), "|cffaaaaaa")
		AddLine("")
		AddLine(T("PG_GUIDE_COMPLETE_NEXT", "You can move on to the next profession expansion."), "|cff66ccff")
		if f.craftBtn then f.craftBtn:Hide() end
		if f.craftAllBtn then f.craftAllBtn:Hide() end
		if f.enchantTargetSlot then f.enchantTargetSlot:Hide() end

		UpdateBuyButtonVisibility()
		return
	end

	if not step then
		AddLine(T("PG_NOT_LEARNED", "This profession expansion is not learned"), "|cffffffaa")
		AddLine("")

		local g = guide or G:GetGuideBySkillLine(skillLineID)

		if g then
			if g.learnViaScroll then
				AddLine(
					T("PG_LEARN_SCROLL", "Buy the learning scroll from the vendor near the trainer:"),
					"|cffaaaaaa"
				)
			elseif g.learnViaQuest then
				AddLine(
					T("PG_LEARN_QUEST", "Start the quest chain to learn this expansion:"),
					"|cffaaaaaa"
				)
			else
				AddLine(
					T("PG_LEARN_TRAINER", "Learn the profession from a trainer:"),
					"|cffaaaaaa"
				)
			end
		else
			AddLine(
				T("PG_LEARN_TRAINER", "Learn the profession from a trainer:"),
				"|cffaaaaaa"
			)
		end
		if f.craftBtn then f.craftBtn:Hide() end
		if f.craftAllBtn then f.craftAllBtn:Hide() end
		if f.enchantTargetSlot then f.enchantTargetSlot:Hide() end

		if g then
			self:RenderTrainers(f, g, ctx)
		end

		return
	end

	if f.prevStepBtn and f.nextStepBtn and f.stepIndicator then
		local steps = G:GetStepsBySkillLine(skillLineID)
		local idx   = G:GetCurrentStepIndex(skillLineID)

		if steps and idx and steps[idx] then

			local total = #steps

			f.prevStepBtn:Show()
			f.nextStepBtn:Show()
			f.stepIndicator:Show()

			f.stepIndicator:SetText(
				string.format(T("PG_STEP_FORMAT", "Step %d / %d"), idx, total)
			)

			if idx <= 1 then
				f.prevStepBtn:Disable()
				f.prevStepBtn.text:SetTextColor(0.5, 0.5, 0.5)
			else
				f.prevStepBtn:Enable()
				f.prevStepBtn.text:SetTextColor(1, 1, 1)
			end

			if idx >= total then
				f.nextStepBtn:Disable()
				f.nextStepBtn.text:SetTextColor(0.5, 0.5, 0.5)
			else
				f.nextStepBtn:Enable()
				f.nextStepBtn.text:SetTextColor(1, 1, 1)
			end
		else
			f.prevStepBtn:Hide()
			f.nextStepBtn:Hide()
			f.stepIndicator:Hide()
		end
		if f.resetStepBtn then
			local autoIdx = G:GetAutoStepIndex(skillLineID)

			if G.state.manualStepIndex
			   and autoIdx
			   and autoIdx ~= G.state.manualStepIndex
			then
				f.resetStepBtn:Show()
			else
				f.resetStepBtn:Hide()
			end

		end

	end

self:RenderHeader(f, guide, skill, step, recipeLearned, AddLine, ctx)
self:RenderNote(f, step, AddLine, ctx)
self:RenderRecipes(f, step, AddLine, ctx)
self:RenderMaterials(f, step, skill, AddLine, ctx, GetTextLine)

local stepRefs = G:GetStepNoteRefs(step)

if stepRefs then
    for _, ref in ipairs(stepRefs) do
        if ref.type == "npc" then
			AddLine("") 

			self:RenderRecipeVendor(
				f,
				{
					npcID = ref.npcID,
					name  = ref.name,
					mapID = ref.mapID,
					x     = ref.x,
					y     = ref.y,
					isDropSource = true, 
				},
				ctx
			)

			ctx.y = ctx.y + 6
			skipDefaultTrainers = true
        end
    end
end

if not skipDefaultTrainers then
    if recipeVendor then
        self:RenderRecipeVendor(f, recipeVendor, ctx)
    else
        self:RenderTrainers(f, guide, ctx)
    end
end

local showCraftButtons = G:IsProfessionWindowOpen(skillLineID)

local isEnchanting = false
if guide and guide.profession == "Enchanting" then
    if step and step.recipes and step.recipes[1] and step.recipes[1].spellID then
        isEnchanting = true
    end
end

if f.craftBtn and f.craftAllBtn then
    if showCraftButtons and recipeLearned then

        if step and step.disablesCraft then
            f.craftBtn:Hide()
        else
            f.craftBtn:Show()
        end

        if step and step.disablesCraftAll then
            f.craftAllBtn:Hide()
        else
            f.craftAllBtn:Show()
        end

    else
        f.craftBtn:Hide()
        f.craftAllBtn:Hide()
        if f.enchantTargetSlot then f.enchantTargetSlot:Hide() end
    end
end

local contentHeight = ctx.y
local footerHeight = 0
if f.footer and f.footer:IsShown() then
    footerHeight = f.footer:GetHeight() + 6 
end

f.content:SetHeight(contentHeight)

local totalHeight =
    FRAME_PADDING_TOP
    + contentHeight
    + footerHeight
    + FRAME_PADDING_BOTTOM
    + 32

local left = f:GetLeft()
local top  = f:GetTop()

f:SetHeight(math.max(totalHeight, FRAME_MIN_HEIGHT))

if left and top then
    f:ClearAllPoints()
    f:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", left, top)
end

UpdateBuyButtonVisibility()

end

function UI:RenderHeader(f, guide, skill, step, recipeLearned, AddLine, ctx)
    AddLine((guide.nameKey and T(guide.nameKey)) or guide.name, "|cffffff00")
	if step and step.experiment then
		AddLine(T("PG_STEP_TYPE_EXPERIMENT", "Step type: experiment"), "|cff66ccff")
	end

	if step and step.recipes and step.recipes[1] then
		local skillLineID = G:GetSelectedSkillLine()
		local remaining = G:GetRemainingCraftsForStep(skillLineID, step)
		local total = step.recipes[1].crafts or 0

		local skill = G:GetSkillForStep(skillLineID, step)
		local target = step.max or "?"

		AddLine(
			T("PG_REMAINING_CRAFTS", "Remaining to craft: ")
			.. (remaining > 0 and "|cff66ccff" or "|cff00ff00")
			.. remaining .. " / " .. total .. "|r"
			.. T("PG_UNTIL_SKILL_PREFIX", "  |cffaaaaaa(until skill ") .. target .. T("PG_UNTIL_SKILL_SUFFIX", ")|r")
		)
	end

	if recipeLearned == false then
		local questRecipe = false

		if step and step.recipes then
			for _, r in ipairs(step.recipes) do
				if r.recipeSource == "quest" then
					questRecipe = true
					break
				end
			end
		end

		if not questRecipe then
			AddLine(T("PG_RECIPE_NOT_LEARNED", "|cffff5555Recipe not learned!|r"))
			AddLine(T("PG_RECIPE_LEARN_HINT", "|cffaaaaaaLearn the recipe|r"))
		end
	end

    if skill == false then
        AddLine(T("PG_MANUAL_STEP_VIEW", "Manual step view"), "|cffaaaaaa")
    elseif type(skill) == "number" then
        local eff, real, bonus = G:GetEffectiveSkillLevel(G:GetSelectedSkillLine())

		if bonus > 0 then
			AddLine(T("PG_SKILL_LEVEL", "Skill level: ")..real..T("PG_SKILL_RACIAL_BONUS_PREFIX", " |cff66ccff(+")..bonus..T("PG_SKILL_RACIAL_BONUS_SUFFIX", " racial)|r"), "|cffaaaaaa")
		else
			AddLine(T("PG_SKILL_LEVEL", "Skill level: ")..real, "|cffaaaaaa")
		end
    end
end

function UI:RenderNote(f, step, AddLine, ctx)
    if not step then return end

    local note = (step.noteKey and T(step.noteKey)) or step.note
    if not note or note == "" then return end
	local refs = BuildNoteRefs(step)

    ctx.y = ctx.y + 8

	local line = GetTextLine(f, ctx.i)
	line:ClearAllPoints()
	line:SetPoint("TOPLEFT", f.content, "TOPLEFT", 0, -ctx.y)
	line:SetWidth(220)
	line:SetHeight(16)   
	line.text:SetWidth(200) 
	line.text:SetWordWrap(true)

    if not line.icon then
        line.icon = line:CreateTexture(nil, "OVERLAY")
        line.icon:SetSize(20, 20)
        line.icon:SetPoint("LEFT", 0, 0)
        line.icon:SetTexture("Interface\\COMMON\\help-i")
		line.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

    end
    line.icon:Show()

    line.text:SetPoint("LEFT", line.icon, "RIGHT", 2, 0)
    line.text:SetText(T("PG_NOTE_LABEL", "|cff66ccffNote|r"))

    line.itemID = nil
    line.itemLink = nil
    line:Show()

	local h = line.text:GetStringHeight()
	line:SetHeight(h)
	ctx.y = ctx.y + h + LINE_SPACING
	ctx.i = ctx.i + 1

	for textLine in string.gmatch(note, "[^\n]+") do
		local l = GetTextLine(f, ctx.i)
		l:ClearAllPoints()

		l:SetPoint("TOPLEFT", f.content, "TOPLEFT", 0, -ctx.y)
		l:SetWidth(TEXT_SAFE_WIDTH)

		l.text:ClearAllPoints()
		l.text:SetPoint("LEFT", 0, 0)
		l.text:SetWidth(TEXT_SAFE_WIDTH)
		l.text:SetWordWrap(true)

		l.text:SetWordWrap(true)
		l.text:SetText("|cffaaaaaa"..textLine.."|r")

		l.itemID = nil
		l.itemLink = nil
		l:Show()

		local h = l.text:GetStringHeight()
		l:SetHeight(h)

		ctx.y = ctx.y + h
		ctx.i = ctx.i + 1
	end
    if refs then
        ctx.y = ctx.y + 4
        for _, text in ipairs(refs) do
            AddLine(text, "|cff66ccff")
        end
    end

    AddLine("")
end


function UI:RenderRecipes(f, step, AddLine, ctx)
	ctx.y = ctx.y + 10
    AddLine(T("PG_CRAFT_NOW", "Craft now:"), "|cffffffaa")

    for _, r in ipairs(step.recipes or {}) do
        local recipeName

        if r.spellID and C_Spell and C_Spell.GetSpellInfo then
            local info = C_Spell.GetSpellInfo(r.spellID)
            recipeName = info and info.name
        elseif r.name then
            recipeName = r.name
        end

        if recipeName then
            AddLine(" • "..recipeName, "|cff66ccff")
        end
    end

    AddLine("")
end

function UI:RenderMaterials(f, step, skill, AddLine, ctx, GetTextLine)
	ctx.y = ctx.y + 12
    AddLine(T("PG_MATERIALS_LIST", "Materials list:"), "|cffffffaa")

    local recipe = step.recipes and step.recipes[1]
    if not recipe then
        AddLine("")
        return
    end

    if recipe.reagentsAny then
		for i, r in ipairs(recipe.reagentsAny) do
			local need = r.count or 0
			local have = G:GetItemCountTotal(r.itemID)

			local line = GetTextLine(f, ctx.i)
			line:ClearAllPoints()
			line:SetPoint("TOPLEFT", f.content, "TOPLEFT", 0, -ctx.y)

			local link = GetCachedItemLink(r.itemID)
			local color = have >= need and "|cff00ff00" or "|cffff5555"
			local prefix = (i == 1) and T("PG_BULLET", " • ") or T("PG_OR_PREFIX", "   OR ")

			line.text:SetText(
				prefix .. link .. " " .. color .. "× " .. need .. "|r  " .. T("PG_HAVE_FORMAT_PREFIX", "|cffaaaaaa(have: ") .. have .. T("PG_HAVE_FORMAT_SUFFIX", ")|r")
			)

			line.itemID   = r.itemID
			line.itemLink = link
			line:Show()

			local h = line.text:GetStringHeight()
			line:SetHeight(h)
			ctx.y = ctx.y + h + LINE_SPACING
			ctx.i = ctx.i + 1
		end

		AddLine("")
		return
	end

    local skillLineID = G:GetSelectedSkillLine()
    local crafts = G:GetRemainingCraftsForStep(skillLineID, step)
    local recipeReagents, reagentState = G:GetResolvedRecipeReagents(skillLineID, recipe)
    local reagents = recipeReagents or recipe.reagents or {}

    if reagentState == "loading" and (not reagents or #reagents == 0) then
        AddLine(T("PG_REAGENTS_LOADING_LINE", " • Loading recipe reagents..."), "|cffaaaaaa")
        AddLine("")
        return
    end

    if not reagents or #reagents == 0 then
        AddLine(T("PG_NO_REAGENT_DATA", " • No reagent data"), "|cff888888")
        AddLine("")
        return
    end

    for _, r in ipairs(reagents) do
        local need = math.max(0, (r.count or 0) * crafts)

        if r.anyOf and r.isMilling then
            local totalHave = 0

            for _, opt in ipairs(r.anyOf) do
                totalHave = totalHave + G:GetItemCountTotal(opt.itemID)
            end

			local needColor  = totalHave >= need and "|cff00ff00" or "|cffff5555"
			local haveColor  = totalHave >= need and "|cff00ff00" or "|cffff5555"

            local line = GetTextLine(f, ctx.i)
            line:ClearAllPoints()
            line:SetPoint("TOPLEFT", f.content, "TOPLEFT", 0, -ctx.y)

			line.text:SetText(
				T("PG_ANY_HERB", " • Any herb × ")
				.. needColor .. need .. "|r  "
				.. haveColor .. T("PG_TOTAL_HAVE_PREFIX", "(total have: ")..totalHave..T("PG_TOTAL_HAVE_SUFFIX", ")|r")
			)

            line.itemID   = nil
            line.itemLink = nil
            line:Show()

			line:SetWidth(TEXT_SAFE_WIDTH)
			line.text:SetWidth(TEXT_SAFE_WIDTH)
			line.text:SetWordWrap(true)

			local h = line.text:GetStringHeight()
			line:SetHeight(h)

			ctx.y = ctx.y + h + LINE_SPACING
			ctx.i = ctx.i + 1

            for _, opt in ipairs(r.anyOf) do
                local have = G:GetItemCountTotal(opt.itemID)
                local link = GetCachedItemLink(opt.itemID)

                local sub = GetTextLine(f, ctx.i)
                sub:ClearAllPoints()
                sub:SetPoint("TOPLEFT", f.content, "TOPLEFT", 12, -ctx.y)

                local haveColor = have >= need and "|cff00ff00" or "|cffff5555"

				sub.text:SetText(
					T("PG_SUB_BULLET", "   - ") .. link .. " "
					.. haveColor .. T("PG_HAVE_PAREN_PREFIX", "(have: ")..have..T("PG_HAVE_PAREN_SUFFIX", ")|r")
				)

                sub.itemID   = opt.itemID
                sub.itemLink = link
                sub:Show()

				sub:SetWidth(TEXT_SAFE_WIDTH - 12)
				sub.text:SetWidth(TEXT_SAFE_WIDTH - 12)
				sub.text:SetWordWrap(true)

				local h = sub.text:GetStringHeight()
				sub:SetHeight(h)

				ctx.y = ctx.y + h + LINE_SPACING
				ctx.i = ctx.i + 1

            end

        elseif r.anyOf then
            local totalHave = 0
            for _, opt in ipairs(r.anyOf) do
                totalHave = totalHave + G:GetItemCountTotal(opt.itemID)
            end

            local bestItemID = GetCheapestAnyOfUIChoice(r)

            if not bestItemID and r.anyOf[1] then
                bestItemID = r.anyOf[1].itemID
            end

            local link = bestItemID
                and GetCachedItemLink(bestItemID)
                or T("PG_ANY_GRADE", "any grade")

            local color = totalHave >= need and "|cff00ff00" or "|cffff5555"

            local line = GetTextLine(f, ctx.i)
			line:SetWidth(TEXT_SAFE_WIDTH)
			line.text:SetWidth(TEXT_SAFE_WIDTH)
			line.text:SetWordWrap(true)
            line:ClearAllPoints()
            line:SetPoint("TOPLEFT", f.content, "TOPLEFT", 0, -ctx.y)

            line.text:SetText(
                " • " .. link .. " " .. color .. "× " .. need ..
                "|r  " .. T("PG_SELECTED_FOR_BUYING", "|cff00ff00(selected for shopping)|r")
            )

            line.itemID   = bestItemID
            line.itemLink = link
            line:Show()

			local h = line.text:GetStringHeight()
			line:SetHeight(h)

			ctx.y = ctx.y + h + LINE_SPACING
			ctx.i = ctx.i + 1

        else
            local have = G:GetItemCountTotal(r.itemID)
            local link = GetCachedItemLink(r.itemID)
            local color = have >= need and "|cff00ff00" or "|cffff5555"

            local line = GetTextLine(f, ctx.i)
            line:ClearAllPoints()
            line:SetPoint("TOPLEFT", f.content, "TOPLEFT", 0, -ctx.y)

			local haveColor = have >= need and "|cff00ff00" or "|cffff5555"

			line.text:SetText(
				" • " .. link .. " "
				.. color .. "× " .. need .. "|r  "
				.. haveColor .. T("PG_HAVE_PAREN_PREFIX", "(have: ")..have..T("PG_HAVE_PAREN_SUFFIX", ")|r")
			)

            line.itemID   = r.itemID
            line.itemLink = link
            line:Show()

			local h = line.text:GetStringHeight()
			line:SetHeight(h)

			ctx.y = ctx.y + h + LINE_SPACING
			ctx.i = ctx.i + 1
        end
    end

    AddLine("")
end

local function CreateTrainerButton(parent)
    local b = CreateFrame("Button", nil, parent)
    b:SetSize(230, 20)
    b:EnableMouse(true)
    CreateButtonBorder(b)

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

    b:SetScript("OnEnter", function(self)
        self.bg:SetColorTexture(1, 1, 1, 0.08)
        if self.border then
            self.border:SetBackdropBorderColor(1, 1, 1, 0.35)
        end
    end)

    b:SetScript("OnLeave", function(self)
        self.bg:SetColorTexture(1, 1, 1, 0)
        if self.border then
            self.border:SetBackdropBorderColor(1, 1, 1, 0.15)
        end
    end)

    b:SetScript("OnClick", function(self)
        Waypoints:SetTrainerWaypoint(self._waypoint)
    end)

    b.text = b:CreateFontString(nil, "OVERLAY")
    b.text:SetFont(FONT, 12)
    b.text:SetPoint("LEFT", 6, 0)

    return b
end

function UI:RenderRecipeVendor(f, vendor, ctx)
    if f.trainerButtons then
        for _, b in ipairs(f.trainerButtons) do
            b:Hide()
        end
    end

    if f.trainersHeader then
        f.trainersHeader:Hide()
    end

    ctx.y = ctx.y + 12
    f.trainersHeader:ClearAllPoints()
    f.trainersHeader:SetPoint("TOPLEFT", f.content, "TOPLEFT", 0, -ctx.y)
    if vendor.isDropSource then
		f.trainersHeader:SetText(T("PG_RECIPE_SOURCE", "Recipe source:"))
	else
		f.trainersHeader:SetText(T("PG_RECIPE_VENDOR", "Recipe vendor:"))
	end

    f.trainersHeader:Show()

    ctx.y = ctx.y + 18

    local b = f.trainerButtons[1]

    if not b then
        b = CreateTrainerButton(f.content)
        f.trainerButtons[1] = b
    end

    b:ClearAllPoints()
    b:SetPoint("TOPLEFT", f.content, "TOPLEFT", 0, -ctx.y)
    b.text:SetText(" " .. (vendor.name or ("NPC " .. (vendor.npcID or "?"))))
    b._waypoint = vendor

    b:Show()
    ctx.y = ctx.y + 22
end

function UI:RenderTrainers(f, guide, ctx)
    if f.trainerButtons then
        for _, b in ipairs(f.trainerButtons) do
            b:Hide()
        end
    end
    if f.trainersHeader then
        f.trainersHeader:Hide()
    end

    if not guide or not guide.trainers then return end

    local faction = (G.GetPlayerFaction and G:GetPlayerFaction())
    if not faction then return end

    local trainers = guide.trainers[faction]
    if not trainers or #trainers == 0 then return end

    ctx.y = ctx.y + 12
    f.trainersHeader:ClearAllPoints()
    f.trainersHeader:SetPoint("TOPLEFT", f.content, "TOPLEFT", 0, -ctx.y)
    f.trainersHeader:Show()

    ctx.y = ctx.y + 18

    for idx, t in ipairs(trainers) do
        local b = f.trainerButtons[idx]

        if not b then
            b = CreateTrainerButton(f.content)
            f.trainerButtons[idx] = b
        end

        b:ClearAllPoints()
        b:SetPoint("TOPLEFT", f.content, "TOPLEFT", 0, -ctx.y)
        b.text:SetText(" " .. (t.name or ("NPC " .. (t.npcID or "?"))))
        b._waypoint = t

        b:Show()
        ctx.y = ctx.y + 22
    end
end

SLASH_AHUIPG1 = "/pg"
SlashCmdList.AHUIPG = function()
    local f = UI.frame or UI:Create()
	if f:IsShown() then
		f:Hide()
	else
		f:Show()
		UpdateBuyButtonVisibility()
		UI:Refresh()
	end

end

UI.frame = UI:Create()
UI.frame.epochBtn:SetScript("OnClick", function(self)
    ShowEpochMenu(self)
end)
UI.frame.profBtn:SetScript("OnClick", function(self)
    ShowProfessionMenu(self)
end)
Shopping.OnAuctionStateChanged = function()
    UpdateBuyButtonVisibility()
end

local pgWatcher = CreateFrame("Frame")

	pgWatcher:RegisterEvent("SKILL_LINES_CHANGED")
	pgWatcher:RegisterEvent("TRADE_SKILL_DATA_SOURCE_CHANGED")
	pgWatcher:RegisterEvent("TRADE_SKILL_CLOSE")
	pgWatcher:RegisterEvent("BAG_UPDATE_DELAYED")
	pgWatcher:RegisterEvent("PLAYER_REGEN_ENABLED")
	pgWatcher:RegisterEvent("NEW_RECIPE_LEARNED")
	pgWatcher:RegisterEvent("TRADE_SKILL_LIST_UPDATE")

pgWatcher:SetScript("OnEvent", function(_, event)
    if not UI.frame or not UI.frame:IsShown() then return end

    local skillLineID = G:GetSelectedSkillLine()
    if not skillLineID then return end

    if event == "NEW_RECIPE_LEARNED"
       or event == "TRADE_SKILL_LIST_UPDATE"
       or event == "TRADE_SKILL_DATA_SOURCE_CHANGED"
    then
        C_Timer.After(0, function()
            if not UI.frame or not UI.frame:IsShown() then return end

            G:ValidateManualStep(skillLineID)
            State:Save()
            UI:Refresh()
        end)

        return
    end

    G:ValidateManualStep(skillLineID)
    State:Save()
    UI:Refresh()
end)

local scaleLoader = CreateFrame("Frame")
scaleLoader:RegisterEvent("PLAYER_LOGIN")

scaleLoader:SetScript("OnEvent", function()
    if not UI.frame then return end

    local cfg = GetPGConfig()
    local scale = cfg.scale or DEFAULT_SCALE

    UI.frame:SetScale(scale)
end)