--------------------------------------------------------------------------------
-- Nanami-UI: Trainer UI (TrainerUI.lua)
-- Replaces ClassTrainerFrame with Nanami-UI styled interface
--------------------------------------------------------------------------------

SFrames = SFrames or {}
SFrames.TrainerUI = {}
local TUI = SFrames.TrainerUI
SFramesDB = SFramesDB or {}

--------------------------------------------------------------------------------
-- Theme (Pink Cat-Paw)
--------------------------------------------------------------------------------
local T = SFrames.Theme:Extend({
    moneyGold   = { 1, 0.84, 0.0 },
    moneySilver = { 0.78, 0.78, 0.78 },
    moneyCopper = { 0.71, 0.43, 0.18 },
    available   = { 0.25, 1.0, 0.25 },
    unavailable = { 0.80, 0.20, 0.20 },
    used        = { 0.50, 0.50, 0.50 },
})

local QUALITY_COLORS = {
    [0] = { 0.62, 0.62, 0.62 }, [1] = { 1, 1, 1 },
    [2] = { 0.12, 1, 0 },       [3] = { 0.0, 0.44, 0.87 },
    [4] = { 0.64, 0.21, 0.93 }, [5] = { 1, 0.5, 0 },
}

local function ColorToQuality(r, g, b)
    if not r then return nil end
    if r > 0.9 and g > 0.35 and g < 0.65 and b < 0.15 then return 5 end
    if r > 0.5 and r < 0.8 and g < 0.35 and b > 0.8 then return 4 end
    if r < 0.15 and g > 0.3 and g < 0.6 and b > 0.7 then return 3 end
    if r < 0.25 and g > 0.85 and b < 0.15 then return 2 end
    if r > 0.5 and r < 0.75 and g > 0.5 and g < 0.75 and b > 0.5 and b < 0.75 then return 0 end
    return 1
end

--------------------------------------------------------------------------------
-- Layout
--------------------------------------------------------------------------------
local FRAME_W       = 380
local FRAME_H       = 540
local HEADER_H      = 34
local SIDE_PAD      = 14
local CONTENT_W     = FRAME_W - SIDE_PAD * 2
local FILTER_H      = 28
local LIST_ROW_H    = 32
local CAT_ROW_H     = 20
local DETAIL_H      = 160
local BOTTOM_H      = 52
local SCROLL_STEP   = 40
local MAX_ROWS      = 60

--------------------------------------------------------------------------------
-- State
--------------------------------------------------------------------------------
local MainFrame      = nil
local selectedIndex  = nil
local currentFilter  = "all"
local displayList    = {}
local rowButtons     = {}
local collapsedCats  = {}
local function HideBlizzardTrainer()
    if not ClassTrainerFrame then return end
    ClassTrainerFrame:SetScript("OnHide", function() end)
    if ClassTrainerFrame:IsVisible() then
        if HideUIPanel then
            pcall(HideUIPanel, ClassTrainerFrame)
        else
            ClassTrainerFrame:Hide()
        end
    end
    ClassTrainerFrame:SetAlpha(0)
    ClassTrainerFrame:EnableMouse(false)
    ClassTrainerFrame:ClearAllPoints()
    ClassTrainerFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMRIGHT", 2000, 2000)
end

--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------
local function GetFont()
    if SFrames and SFrames.GetFont then return SFrames:GetFont() end
    return "Fonts\\ARIALN.TTF"
end

local function SetRoundBackdrop(frame)
    frame:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 14,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    frame:SetBackdropColor(T.panelBg[1], T.panelBg[2], T.panelBg[3], T.panelBg[4])
    frame:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], T.panelBorder[4])
end

local function CreateShadow(parent)
    local s = CreateFrame("Frame", nil, parent)
    s:SetPoint("TOPLEFT", parent, "TOPLEFT", -4, 4)
    s:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 4, -4)
    s:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 16,
        insets = { left = 4, right = 4, top = 4, bottom = 4 },
    })
    s:SetBackdropColor(0, 0, 0, 0.45)
    s:SetBackdropBorderColor(0, 0, 0, 0.6)
    s:SetFrameLevel(math.max(0, parent:GetFrameLevel() - 1))
    return s
end

local function FormatMoneyText(copper)
    if not copper or copper <= 0 then return "" end
    local g = math.floor(copper / 10000)
    local s = math.floor(math.mod(copper, 10000) / 100)
    local c = math.mod(copper, 100)
    local parts = {}
    if g > 0 then table.insert(parts, "|cffffd700" .. g .. "g|r") end
    if s > 0 then table.insert(parts, "|cffc7c7cf" .. s .. "s|r") end
    if c > 0 then table.insert(parts, "|cffeda55f" .. c .. "c|r") end
    if table.getn(parts) == 0 then return "|cffc7c7cf0c|r" end
    return table.concat(parts, " ")
end

local function SetMoneyFrame(moneyFrame, value)
    if not moneyFrame then return end
    value = tonumber(value) or 0
    if value < 0 then value = 0 end
    if SmallMoneyFrame_SetAmount then
        pcall(SmallMoneyFrame_SetAmount, moneyFrame, value)
    elseif MoneyFrame_Update and moneyFrame.GetName then
        local name = moneyFrame:GetName()
        if name and name ~= "" then
            pcall(MoneyFrame_Update, name, value)
        end
    end
end

local function IsServiceHeader(index)
    local name, _, category = GetTrainerServiceInfo(index)
    if not name then return false end
    local hasIcon = GetTrainerServiceIcon and GetTrainerServiceIcon(index)
    if not hasIcon or hasIcon == "" then return true end
    local ok, cost = pcall(GetTrainerServiceCost, index)
    if not ok then return true end
    if category ~= "available" and category ~= "unavailable" and category ~= "used" then
        return true
    end
    return false
end

local scanTip = nil

local function GetServiceTooltipInfo(index)
    if not scanTip then
        scanTip = CreateFrame("GameTooltip", "SFramesTrainerScanTip", nil, "GameTooltipTemplate")
    end
    scanTip:SetOwner(WorldFrame, "ANCHOR_NONE")
    scanTip:ClearLines()
    local ok = pcall(scanTip.SetTrainerService, scanTip, index)
    if not ok then return "", "" end

    local infoLines = {}
    local descLines = {}
    local numLines = scanTip:NumLines()
    local foundDesc = false

    for i = 2, numLines do
        local leftFS = _G["SFramesTrainerScanTipTextLeft" .. i]
        local rightFS = _G["SFramesTrainerScanTipTextRight" .. i]
        local leftText = leftFS and leftFS:GetText() or ""
        local rightText = rightFS and rightFS:GetText() or ""

        if leftText == "" and rightText == "" then
            if not foundDesc and table.getn(infoLines) > 0 then
                foundDesc = true
            end
        else
            local line
            if rightText ~= "" and leftText ~= "" then
                line = leftText .. "    " .. rightText
            elseif leftText ~= "" then
                line = leftText
            else
                line = rightText
            end

            local isYellow = leftFS and leftFS.GetTextColor and true
            local r, g, b
            if leftFS and leftFS.GetTextColor then
                r, g, b = leftFS:GetTextColor()
            end
            local isWhiteOrYellow = r and (r > 0.9 and g > 0.75)

            if not foundDesc and rightText ~= "" then
                table.insert(infoLines, line)
            elseif not foundDesc and not isWhiteOrYellow and string.len(leftText) < 30 then
                table.insert(infoLines, line)
            else
                foundDesc = true
                table.insert(descLines, line)
            end
        end
    end
    scanTip:Hide()
    return table.concat(infoLines, "\n"), table.concat(descLines, "\n")
end

local function GetServiceQuality(index)
    if not scanTip then
        scanTip = CreateFrame("GameTooltip", "SFramesTrainerScanTip", nil, "GameTooltipTemplate")
    end
    scanTip:SetOwner(WorldFrame, "ANCHOR_NONE")
    scanTip:ClearLines()
    local ok = pcall(scanTip.SetTrainerService, scanTip, index)
    if not ok then scanTip:Hide() return nil end
    local firstLine = _G["SFramesTrainerScanTipTextLeft1"]
    if not firstLine or not firstLine.GetTextColor then
        scanTip:Hide()
        return nil
    end
    local r, g, b = firstLine:GetTextColor()
    scanTip:Hide()
    return ColorToQuality(r, g, b)
end

--------------------------------------------------------------------------------
-- Filter Button Factory
--------------------------------------------------------------------------------
local function CreateFilterBtn(parent, text, w)
    local btn = CreateFrame("Button", nil, parent)
    btn:SetWidth(w or 60)
    btn:SetHeight(20)
    btn:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        tile = false, tileSize = 0, edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    btn:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
    btn:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], 0.5)

    local fs = btn:CreateFontString(nil, "OVERLAY")
    fs:SetFont(GetFont(), 11, "OUTLINE")
    fs:SetPoint("CENTER", 0, 0)
    fs:SetText(text)
    fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
    btn.label = fs

    btn:SetScript("OnEnter", function()
        this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
        this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
    end)
    btn:SetScript("OnLeave", function()
        if this.active then
            this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
            this:SetBackdropBorderColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 1)
        else
            this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
            this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], 0.5)
        end
    end)

    function btn:SetActive(flag)
        self.active = flag
        if flag then
            self:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
            self:SetBackdropBorderColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 1)
            self.label:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3])
        else
            self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
            self:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], 0.5)
            self.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
        end
    end

    return btn
end

--------------------------------------------------------------------------------
-- Action Button Factory
--------------------------------------------------------------------------------
local function CreateActionBtn(parent, text, w)
    local btn = CreateFrame("Button", nil, parent)
    btn:SetWidth(w or 100)
    btn:SetHeight(28)
    btn:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        tile = false, tileSize = 0, edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    btn:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
    btn:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])

    local fs = btn:CreateFontString(nil, "OVERLAY")
    fs:SetFont(GetFont(), 12, "OUTLINE")
    fs:SetPoint("CENTER", 0, 0)
    fs:SetText(text)
    fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
    btn.label = fs

    btn.disabled = false
    function btn:SetDisabled(flag)
        self.disabled = flag
        if flag then
            self.label:SetTextColor(T.btnDisabledText[1], T.btnDisabledText[2], T.btnDisabledText[3])
            self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], 0.5)
        else
            self.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
            self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
        end
    end

    btn:SetScript("OnEnter", function()
        if not this.disabled then
            this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
            this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
            this.label:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3])
        end
    end)
    btn:SetScript("OnLeave", function()
        if not this.disabled then
            this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
            this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
            this.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
        end
    end)

    return btn
end

--------------------------------------------------------------------------------
-- Quality cache (lazy per-row instead of bulk tooltip scan)
--------------------------------------------------------------------------------
local qualityCache = {}

local function GetCachedServiceQuality(index)
    if qualityCache[index] ~= nil then return qualityCache[index] end
    local q = GetServiceQuality(index)
    qualityCache[index] = q or false
    return q
end

--------------------------------------------------------------------------------
-- List Row Factory (reusable for both headers and services)
--------------------------------------------------------------------------------
local function CreateListRow(parent, idx)
    local row = CreateFrame("Button", nil, parent)
    row:SetWidth(CONTENT_W)
    row:SetHeight(LIST_ROW_H)

    local iconFrame = CreateFrame("Frame", nil, row)
    iconFrame:SetWidth(LIST_ROW_H - 4)
    iconFrame:SetHeight(LIST_ROW_H - 4)
    iconFrame:SetPoint("LEFT", row, "LEFT", 0, 0)
    iconFrame:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 12,
        insets = { left = 2, right = 2, top = 2, bottom = 2 },
    })
    iconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
    iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
    row.iconFrame = iconFrame

    local icon = iconFrame:CreateTexture(nil, "ARTWORK")
    icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
    icon:SetPoint("TOPLEFT", iconFrame, "TOPLEFT", 3, -3)
    icon:SetPoint("BOTTOMRIGHT", iconFrame, "BOTTOMRIGHT", -3, 3)
    row.icon = icon

    local qualGlow = iconFrame:CreateTexture(nil, "OVERLAY")
    qualGlow:SetTexture("Interface\\Buttons\\UI-ActionButton-Border")
    qualGlow:SetBlendMode("ADD")
    qualGlow:SetAlpha(0.7)
    qualGlow:SetWidth((LIST_ROW_H - 4) * 1.8)
    qualGlow:SetHeight((LIST_ROW_H - 4) * 1.8)
    qualGlow:SetPoint("CENTER", iconFrame, "CENTER", 0, 0)
    qualGlow:Hide()
    row.qualGlow = qualGlow

    local nameFS = row:CreateFontString(nil, "OVERLAY")
    nameFS:SetFont(GetFont(), 12, "OUTLINE")
    nameFS:SetPoint("LEFT", iconFrame, "RIGHT", 6, 2)
    nameFS:SetPoint("RIGHT", row, "RIGHT", -90, 0)
    nameFS:SetJustifyH("LEFT")
    nameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
    row.nameFS = nameFS

    local subFS = row:CreateFontString(nil, "OVERLAY")
    subFS:SetFont(GetFont(), 10, "OUTLINE")
    subFS:SetPoint("TOPLEFT", nameFS, "BOTTOMLEFT", 0, -1)
    subFS:SetJustifyH("LEFT")
    subFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
    row.subFS = subFS

    local costFS = row:CreateFontString(nil, "OVERLAY")
    costFS:SetFont(GetFont(), 11, "OUTLINE")
    costFS:SetPoint("RIGHT", row, "RIGHT", -4, 0)
    costFS:SetJustifyH("RIGHT")
    costFS:Hide()
    row.costFS = costFS

    local costMoney = CreateFrame("Frame", "SFTRow" .. idx .. "Money", row, "SmallMoneyFrameTemplate")
    costMoney:SetPoint("RIGHT", row, "RIGHT", -2, 0)
    costMoney:SetWidth(100)
    costMoney:SetHeight(14)
    costMoney:SetFrameLevel(row:GetFrameLevel() + 2)
    costMoney:SetScale(0.85)
    costMoney:UnregisterAllEvents()
    costMoney:SetScript("OnEvent", nil)
    costMoney:SetScript("OnShow", nil)
    costMoney.moneyType = nil
    costMoney.hasPickup = nil
    costMoney.small = 1
    row.costMoney = costMoney

    local catFS = row:CreateFontString(nil, "OVERLAY")
    catFS:SetFont(GetFont(), 11, "OUTLINE")
    catFS:SetPoint("LEFT", row, "LEFT", 4, 0)
    catFS:SetJustifyH("LEFT")
    catFS:SetTextColor(T.catHeader[1], T.catHeader[2], T.catHeader[3])
    catFS:Hide()
    row.catFS = catFS

    local catSep = row:CreateTexture(nil, "ARTWORK")
    catSep:SetTexture("Interface\\Buttons\\WHITE8X8")
    catSep:SetHeight(1)
    catSep:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
    catSep:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", 0, 0)
    catSep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], 0.3)
    catSep:Hide()
    row.catSep = catSep

    local highlight = row:CreateTexture(nil, "HIGHLIGHT")
    highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
    highlight:SetBlendMode("ADD")
    highlight:SetAllPoints(row)
    highlight:SetAlpha(0.3)
    row.highlight = highlight

    local selectedBg = row:CreateTexture(nil, "ARTWORK")
    selectedBg:SetTexture("Interface\\Buttons\\WHITE8X8")
    selectedBg:SetAllPoints(row)
    selectedBg:SetVertexColor(T.selectedRowBg[1], T.selectedRowBg[2], T.selectedRowBg[3], 0.40)
    selectedBg:Hide()
    row.selectedBg = selectedBg

    local selectedGlow = row:CreateTexture(nil, "ARTWORK")
    selectedGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
    selectedGlow:SetPoint("TOPLEFT", row, "TOPLEFT", 0, 0)
    selectedGlow:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
    selectedGlow:SetWidth(4)
    selectedGlow:SetVertexColor(1, 0.65, 0.85, 1)
    selectedGlow:Hide()
    row.selectedGlow = selectedGlow

    local selTop = row:CreateTexture(nil, "OVERLAY")
    selTop:SetTexture("Interface\\Buttons\\WHITE8X8")
    selTop:SetHeight(1)
    selTop:SetPoint("TOPLEFT", row, "TOPLEFT", 0, 0)
    selTop:SetPoint("TOPRIGHT", row, "TOPRIGHT", 0, 0)
    selTop:SetVertexColor(T.selectedRowBorder[1], T.selectedRowBorder[2], T.selectedRowBorder[3], T.selectedRowBorder[4])
    selTop:Hide()
    row.selTop = selTop

    local selBot = row:CreateTexture(nil, "OVERLAY")
    selBot:SetTexture("Interface\\Buttons\\WHITE8X8")
    selBot:SetHeight(1)
    selBot:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
    selBot:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", 0, 0)
    selBot:SetVertexColor(T.selectedRowBorder[1], T.selectedRowBorder[2], T.selectedRowBorder[3], T.selectedRowBorder[4])
    selBot:Hide()
    row.selBot = selBot

    row.serviceIndex = nil
    row.isHeader = false

    row:SetScript("OnEnter", function()
        if this.serviceIndex and not this.isHeader then
            GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
            local ok = pcall(GameTooltip.SetTrainerService, GameTooltip, this.serviceIndex)
            if ok then
                GameTooltip:Show()
            else
                GameTooltip:Hide()
            end
        end
    end)
    row:SetScript("OnLeave", function() GameTooltip:Hide() end)

    function row:SetAsHeader(name, collapsed)
        self.isHeader = true
        self:SetHeight(CAT_ROW_H)
        self.iconFrame:Hide()
        self.qualGlow:Hide()
        self.nameFS:Hide()
        self.subFS:Hide()
        self.costFS:Hide()
        self.costMoney:Hide()
        self.selectedBg:Hide()
        self.selectedGlow:Hide()
        self.selTop:Hide()
        self.selBot:Hide()
        self.highlight:SetAlpha(0.15)
        local arrow = collapsed and "+" or "-"
        self.catFS:SetText(arrow .. "  " .. (name or ""))
        self.catFS:Show()
        self.catSep:Show()
    end

    function row:SetAsService(svc)
        self.isHeader = false
        self.serviceIndex = svc.index
        self:SetHeight(LIST_ROW_H)
        self.iconFrame:Show()
        self.nameFS:Show()
        self.subFS:Show()
        self.costFS:Hide()
        self.costMoney:Show()
        self.catFS:Hide()
        self.catSep:Hide()
        self.highlight:SetAlpha(0.3)

        local iconTex = GetTrainerServiceIcon and GetTrainerServiceIcon(svc.index)
        self.icon:SetTexture(iconTex)
        self.nameFS:SetText(svc.name)
        self.subFS:SetText(svc.subText)

        if svc.category == "available" then
            self.nameFS:SetTextColor(T.available[1], T.available[2], T.available[3])
            self.icon:SetVertexColor(1, 1, 1)
        elseif svc.category == "unavailable" then
            self.nameFS:SetTextColor(T.unavailable[1], T.unavailable[2], T.unavailable[3])
            self.icon:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3])
        else
            self.nameFS:SetTextColor(T.used[1], T.used[2], T.used[3])
            self.icon:SetVertexColor(T.passive[1], T.passive[2], T.passive[3])
        end

        local quality = GetCachedServiceQuality(svc.index)
        local qc = QUALITY_COLORS[quality]
        if qc and quality and quality >= 2 then
            self.qualGlow:SetVertexColor(qc[1], qc[2], qc[3])
            self.qualGlow:Show()
            self.iconFrame:SetBackdropBorderColor(qc[1], qc[2], qc[3], 1)
        else
            self.qualGlow:Hide()
            self.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
        end

        local ok, cost = pcall(GetTrainerServiceCost, svc.index)
        if ok and cost and cost > 0 then
            SetMoneyFrame(self.costMoney, cost)
        else
            SetMoneyFrame(self.costMoney, 0)
        end
    end

    function row:Clear()
        self.serviceIndex = nil
        self.isHeader = false
        self.qualGlow:Hide()
        self.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
        self.selectedBg:Hide()
        self.selectedGlow:Hide()
        self.selTop:Hide()
        self.selBot:Hide()
        self.costMoney:Hide()
        self:Hide()
    end

    return row
end

--------------------------------------------------------------------------------
-- Build Display List (with categories)
--------------------------------------------------------------------------------
local function BuildDisplayList()
    displayList = {}
    local numServices = GetNumTrainerServices and GetNumTrainerServices() or 0
    if numServices == 0 then return end

    local currentCat = nil
    local catServices = {}
    local categories = {}
    local catOrder = {}

    for i = 1, numServices do
        local name, subText, category = GetTrainerServiceInfo(i)
        if name then
            local isHdr = IsServiceHeader(i)
            if isHdr then
                currentCat = name
                if not catServices[name] then
                    catServices[name] = {}
                    table.insert(catOrder, name)
                end
            else
                if not currentCat then
                    currentCat = "技能"
                    if not catServices[currentCat] then
                        catServices[currentCat] = {}
                        table.insert(catOrder, currentCat)
                    end
                end
                local show = false
                if currentFilter == "all" then
                    show = true
                elseif currentFilter == (category or "") then
                    show = true
                end
                if show then
                    table.insert(catServices[currentCat], {
                        index = i,
                        name = name,
                        subText = subText or "",
                        category = category or "unavailable",
                    })
                end
            end
        end
    end

    local hasCats = table.getn(catOrder) > 1

    for _, catName in ipairs(catOrder) do
        local svcs = catServices[catName]
        if table.getn(svcs) > 0 then
            if hasCats then
                table.insert(displayList, {
                    type = "header",
                    name = catName,
                    collapsed = collapsedCats[catName],
                })
            end
            if not collapsedCats[catName] then
                for _, svc in ipairs(svcs) do
                    table.insert(displayList, {
                        type = "service",
                        data = svc,
                    })
                end
            end
        end
    end

    if table.getn(catOrder) <= 1 and table.getn(displayList) == 0 then
        local allCat = catOrder[1] or "技能"
        local svcs = catServices[allCat] or {}
        for _, svc in ipairs(svcs) do
            table.insert(displayList, { type = "service", data = svc })
        end
    end
end

--------------------------------------------------------------------------------
-- Update Functions
--------------------------------------------------------------------------------
local function UpdateList()
    if not MainFrame or not MainFrame:IsVisible() then return end

    BuildDisplayList()

    local content = MainFrame.listScroll.content
    local count = table.getn(displayList)
    local y = 0

    for i = 1, MAX_ROWS do
        local row = rowButtons[i]
        if i <= count then
            local entry = displayList[i]
            row:ClearAllPoints()

            if entry.type == "header" then
                row:SetAsHeader(entry.name, entry.collapsed)
                row:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y)
                row.catName = entry.name
                row:Show()
                y = y + CAT_ROW_H
            else
                row:SetAsService(entry.data)
                row:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y)
                row.catName = nil
                row:Show()
                y = y + LIST_ROW_H

                if selectedIndex == entry.data.index then
                    row.iconFrame:SetBackdropBorderColor(1, 0.65, 0.85, 1)
                    row.iconFrame:SetBackdropColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 0.5)
                    row.selectedBg:Show()
                    row.selectedGlow:Show()
                    row.selTop:Show()
                    row.selBot:Show()
                    row.nameFS:SetTextColor(T.selectedNameText[1], T.selectedNameText[2], T.selectedNameText[3])
                else
                    row.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
                    row.iconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
                    row.selectedBg:Hide()
                    row.selectedGlow:Hide()
                    row.selTop:Hide()
                    row.selBot:Hide()
                end
            end
        else
            row:Clear()
        end
    end

    content:SetHeight(math.max(1, y))
end

local function UpdateDetail()
    if not MainFrame then return end
    local detail = MainFrame.detail

    if not selectedIndex then
        detail.iconFrame:Hide()
        detail.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
        detail.nameFS:SetText("")
        detail.subFS:SetText("")
        detail.reqFS:SetText("")
        detail.infoFS:SetText("")
        detail.descFS:SetText("")
        detail.descDivider:Hide()
        MainFrame.trainBtn:SetDisabled(true)
        SetMoneyFrame(MainFrame.costMoney, 0)
        SetMoneyFrame(MainFrame.availMoney, 0)
        MainFrame.costLabel:SetText("花费:")
        return
    end

    local name, subText, category = GetTrainerServiceInfo(selectedIndex)
    local iconTex = GetTrainerServiceIcon and GetTrainerServiceIcon(selectedIndex)
    local ok, cost = pcall(GetTrainerServiceCost, selectedIndex)
    if not ok then cost = 0 end
    local levelReq = 0
    if GetTrainerServiceLevelReq then
        local ok2, lr = pcall(GetTrainerServiceLevelReq, selectedIndex)
        if ok2 then levelReq = lr or 0 end
    end

    detail.icon:SetTexture(iconTex)
    detail.iconFrame:Show()

    local quality = GetServiceQuality(selectedIndex)
    local qc = QUALITY_COLORS[quality]
    if qc and quality and quality >= 2 then
        detail.iconFrame:SetBackdropBorderColor(qc[1], qc[2], qc[3], 1)
    else
        detail.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
    end

    detail.nameFS:SetText(name or "")

    if category == "available" then
        detail.nameFS:SetTextColor(T.available[1], T.available[2], T.available[3])
    elseif category == "unavailable" then
        detail.nameFS:SetTextColor(T.unavailable[1], T.unavailable[2], T.unavailable[3])
    else
        detail.nameFS:SetTextColor(T.used[1], T.used[2], T.used[3])
    end

    detail.subFS:SetText(subText or "")

    local reqParts = {}
    if levelReq and levelReq > 0 then
        local pLvl = UnitLevel("player") or 60
        local color = pLvl >= levelReq and "|cff40ff40" or "|cffff4040"
        table.insert(reqParts, color .. "需要等级 " .. levelReq .. "|r")
    end
    if GetTrainerServiceSkillReq then
        local ok3, skillName, skillRank, hasReq = pcall(GetTrainerServiceSkillReq, selectedIndex)
        if ok3 and skillName and skillName ~= "" then
            local color = hasReq and "|cff40ff40" or "|cffff4040"
            table.insert(reqParts, color .. "需要 " .. skillName .. " (" .. (skillRank or 0) .. ")|r")
        end
    end
    detail.reqFS:SetText(table.concat(reqParts, "  "))

    local spellInfo, descText = GetServiceTooltipInfo(selectedIndex)
    detail.infoFS:SetText(spellInfo)
    detail.descFS:SetText(descText)
    detail.descDivider:Show()

    local textH = detail.descFS:GetHeight() or 40
    detail.descScroll:GetScrollChild():SetHeight(math.max(1, textH))
    detail.descScroll:SetVerticalScroll(0)

    local canTrain = (category == "available") and cost and (GetMoney() >= cost)
    MainFrame.trainBtn:SetDisabled(not canTrain)

    if cost and cost > 0 then
        MainFrame.costLabel:SetText("花费:")
        SetMoneyFrame(MainFrame.costMoney, cost)
    else
        MainFrame.costLabel:SetText("花费: 免费")
        SetMoneyFrame(MainFrame.costMoney, 0)
    end
    SetMoneyFrame(MainFrame.availMoney, GetMoney())
end

local function UpdateFilters()
    if not MainFrame then return end
    MainFrame.filterAll:SetActive(currentFilter == "all")
    MainFrame.filterAvail:SetActive(currentFilter == "available")
    MainFrame.filterUnavail:SetActive(currentFilter == "unavailable")
    MainFrame.filterUsed:SetActive(currentFilter == "used")
end

local function FullUpdate()
    UpdateFilters()
    UpdateList()
    UpdateDetail()
end

local function SelectService(index)
    selectedIndex = index
    local ok = pcall(SelectTrainerService, index)
    FullUpdate()
end

local function ToggleCategory(catName)
    if collapsedCats[catName] then
        collapsedCats[catName] = nil
    else
        collapsedCats[catName] = true
    end
    FullUpdate()
end

--------------------------------------------------------------------------------
-- Initialize
--------------------------------------------------------------------------------
function TUI:Initialize()
    if MainFrame then return end

    MainFrame = CreateFrame("Frame", "SFramesTrainerFrame", UIParent)
    MainFrame:SetWidth(FRAME_W)
    MainFrame:SetHeight(FRAME_H)
    MainFrame:SetPoint("LEFT", UIParent, "LEFT", 64, 0)
    MainFrame:SetFrameStrata("HIGH")
    MainFrame:SetToplevel(true)
    MainFrame:EnableMouse(true)
    MainFrame:SetMovable(true)
    MainFrame:RegisterForDrag("LeftButton")
    MainFrame:SetScript("OnDragStart", function() this:StartMoving() end)
    MainFrame:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)

    SetRoundBackdrop(MainFrame)
    CreateShadow(MainFrame)

    -- Header
    local header = CreateFrame("Frame", nil, MainFrame)
    header:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", 0, 0)
    header:SetPoint("TOPRIGHT", MainFrame, "TOPRIGHT", 0, 0)
    header:SetHeight(HEADER_H)
    header:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })
    header:SetBackdropColor(T.headerBg[1], T.headerBg[2], T.headerBg[3], T.headerBg[4])

    local titleIco = SFrames:CreateIcon(header, "spellbook", 16)
    titleIco:SetDrawLayer("OVERLAY")
    titleIco:SetPoint("LEFT", header, "LEFT", SIDE_PAD, 0)
    titleIco:SetVertexColor(T.gold[1], T.gold[2], T.gold[3])

    local npcNameFS = header:CreateFontString(nil, "OVERLAY")
    npcNameFS:SetFont(GetFont(), 14, "OUTLINE")
    npcNameFS:SetPoint("LEFT", titleIco, "RIGHT", 5, 0)
    npcNameFS:SetPoint("RIGHT", header, "RIGHT", -30, 0)
    npcNameFS:SetJustifyH("LEFT")
    npcNameFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
    MainFrame.npcNameFS = npcNameFS

    local closeBtn = CreateFrame("Button", nil, header)
    closeBtn:SetWidth(20); closeBtn:SetHeight(20)
    closeBtn:SetPoint("TOPRIGHT", header, "TOPRIGHT", -8, -6)
    local closeTex = closeBtn:CreateTexture(nil, "ARTWORK")
    closeTex:SetTexture("Interface\\AddOns\\Nanami-UI\\img\\icon")
    closeTex:SetTexCoord(0.25, 0.375, 0, 0.125)
    closeTex:SetAllPoints()
    closeTex:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3])
    closeBtn:SetScript("OnClick", function() MainFrame:Hide() end)
    closeBtn:SetScript("OnEnter", function() closeTex:SetVertexColor(1, 0.6, 0.7) end)
    closeBtn:SetScript("OnLeave", function() closeTex:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3]) end)

    local headerSep = MainFrame:CreateTexture(nil, "ARTWORK")
    headerSep:SetTexture("Interface\\Buttons\\WHITE8X8")
    headerSep:SetHeight(1)
    headerSep:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", 6, -HEADER_H)
    headerSep:SetPoint("TOPRIGHT", MainFrame, "TOPRIGHT", -6, -HEADER_H)
    headerSep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])

    -- Filter bar
    local filterBar = CreateFrame("Frame", nil, MainFrame)
    filterBar:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", SIDE_PAD, -(HEADER_H + 4))
    filterBar:SetPoint("TOPRIGHT", MainFrame, "TOPRIGHT", -SIDE_PAD, -(HEADER_H + 4))
    filterBar:SetHeight(FILTER_H)

    local fAll = CreateFilterBtn(filterBar, "全部", 52)
    fAll:SetPoint("LEFT", filterBar, "LEFT", 0, 0)
    fAll:SetScript("OnClick", function() currentFilter = "all"; FullUpdate() end)
    MainFrame.filterAll = fAll

    local fAvail = CreateFilterBtn(filterBar, "可学习", 60)
    fAvail:SetPoint("LEFT", fAll, "RIGHT", 4, 0)
    fAvail:SetScript("OnClick", function() currentFilter = "available"; FullUpdate() end)
    MainFrame.filterAvail = fAvail

    local fUnavail = CreateFilterBtn(filterBar, "不可学", 60)
    fUnavail:SetPoint("LEFT", fAvail, "RIGHT", 4, 0)
    fUnavail:SetScript("OnClick", function() currentFilter = "unavailable"; FullUpdate() end)
    MainFrame.filterUnavail = fUnavail

    local fUsed = CreateFilterBtn(filterBar, "已学会", 60)
    fUsed:SetPoint("LEFT", fUnavail, "RIGHT", 4, 0)
    fUsed:SetScript("OnClick", function() currentFilter = "used"; FullUpdate() end)
    MainFrame.filterUsed = fUsed

    -- Scrollable list area
    local listTop = HEADER_H + FILTER_H + 8
    local listBottom = DETAIL_H + BOTTOM_H + 8

    local listScroll = CreateFrame("ScrollFrame", "SFramesTrainerListScroll", MainFrame)
    listScroll:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", SIDE_PAD, -listTop)
    listScroll:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -SIDE_PAD, listBottom)

    local listContent = CreateFrame("Frame", "SFramesTrainerListContent", listScroll)
    listContent:SetWidth(CONTENT_W)
    listContent:SetHeight(1)
    listScroll:SetScrollChild(listContent)

    listScroll:EnableMouseWheel(true)
    listScroll:SetScript("OnMouseWheel", function()
        local cur = this:GetVerticalScroll()
        local maxVal = this:GetVerticalScrollRange()
        if arg1 > 0 then
            this:SetVerticalScroll(math.max(0, cur - SCROLL_STEP))
        else
            this:SetVerticalScroll(math.min(maxVal, cur + SCROLL_STEP))
        end
    end)
    listScroll.content = listContent
    MainFrame.listScroll = listScroll

    for i = 1, MAX_ROWS do
        local row = CreateListRow(listContent, i)
        row:SetScript("OnClick", function()
            if this.isHeader and this.catName then
                ToggleCategory(this.catName)
            elseif this.serviceIndex then
                SelectService(this.serviceIndex)
            end
        end)
        rowButtons[i] = row
    end

    -- Detail separator
    local detailSep = MainFrame:CreateTexture(nil, "ARTWORK")
    detailSep:SetTexture("Interface\\Buttons\\WHITE8X8")
    detailSep:SetHeight(1)
    detailSep:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", 6, DETAIL_H + BOTTOM_H + 4)
    detailSep:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -6, DETAIL_H + BOTTOM_H + 4)
    detailSep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])

    -- Detail area
    local detail = CreateFrame("Frame", nil, MainFrame)
    detail:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", SIDE_PAD, BOTTOM_H + 4)
    detail:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -SIDE_PAD, BOTTOM_H + 4)
    detail:SetHeight(DETAIL_H)
    MainFrame.detail = detail

    local dIconFrame = CreateFrame("Frame", nil, detail)
    dIconFrame:SetWidth(42); dIconFrame:SetHeight(42)
    dIconFrame:SetPoint("TOPLEFT", detail, "TOPLEFT", 2, -6)
    dIconFrame:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 14,
        insets = { left = 2, right = 2, top = 2, bottom = 2 },
    })
    dIconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
    dIconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
    dIconFrame:Hide()
    detail.iconFrame = dIconFrame

    local dIcon = dIconFrame:CreateTexture(nil, "ARTWORK")
    dIcon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
    dIcon:SetPoint("TOPLEFT", dIconFrame, "TOPLEFT", 3, -3)
    dIcon:SetPoint("BOTTOMRIGHT", dIconFrame, "BOTTOMRIGHT", -3, 3)
    detail.icon = dIcon

    local dNameFS = detail:CreateFontString(nil, "OVERLAY")
    dNameFS:SetFont(GetFont(), 13, "OUTLINE")
    dNameFS:SetPoint("TOPLEFT", dIconFrame, "TOPRIGHT", 8, -2)
    dNameFS:SetPoint("RIGHT", detail, "RIGHT", -4, 0)
    dNameFS:SetJustifyH("LEFT")
    dNameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
    detail.nameFS = dNameFS

    local dSubFS = detail:CreateFontString(nil, "OVERLAY")
    dSubFS:SetFont(GetFont(), 11, "OUTLINE")
    dSubFS:SetPoint("TOPLEFT", dNameFS, "BOTTOMLEFT", 0, -2)
    dSubFS:SetJustifyH("LEFT")
    dSubFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
    detail.subFS = dSubFS

    local dReqFS = detail:CreateFontString(nil, "OVERLAY")
    dReqFS:SetFont(GetFont(), 11, "OUTLINE")
    dReqFS:SetPoint("TOPLEFT", dIconFrame, "BOTTOMLEFT", 0, -6)
    dReqFS:SetPoint("RIGHT", detail, "RIGHT", -4, 0)
    dReqFS:SetJustifyH("LEFT")
    detail.reqFS = dReqFS

    local dInfoFS = detail:CreateFontString(nil, "OVERLAY")
    dInfoFS:SetFont(GetFont(), 11)
    dInfoFS:SetPoint("TOPLEFT", dReqFS, "BOTTOMLEFT", 0, -4)
    dInfoFS:SetPoint("RIGHT", detail, "RIGHT", -4, 0)
    dInfoFS:SetJustifyH("LEFT")
    dInfoFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
    detail.infoFS = dInfoFS

    local descDivider = detail:CreateTexture(nil, "ARTWORK")
    descDivider:SetTexture("Interface\\Buttons\\WHITE8X8")
    descDivider:SetHeight(1)
    descDivider:SetPoint("TOPLEFT", dInfoFS, "BOTTOMLEFT", 0, -5)
    descDivider:SetPoint("RIGHT", detail, "RIGHT", -4, 0)
    descDivider:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], 0.3)
    detail.descDivider = descDivider

    local descScroll = CreateFrame("ScrollFrame", nil, detail)
    descScroll:SetPoint("TOPLEFT", descDivider, "BOTTOMLEFT", 0, -4)
    descScroll:SetPoint("BOTTOMRIGHT", detail, "BOTTOMRIGHT", -4, 2)
    descScroll:EnableMouseWheel(true)
    descScroll:SetScript("OnMouseWheel", function()
        local cur = this:GetVerticalScroll()
        local maxVal = this:GetVerticalScrollRange()
        if arg1 > 0 then
            this:SetVerticalScroll(math.max(0, cur - 14))
        else
            this:SetVerticalScroll(math.min(maxVal, cur + 14))
        end
    end)
    detail.descScroll = descScroll

    local descContent = CreateFrame("Frame", nil, descScroll)
    descContent:SetWidth(CONTENT_W - 8)
    descContent:SetHeight(1)
    descScroll:SetScrollChild(descContent)

    local dDescFS = descContent:CreateFontString(nil, "OVERLAY")
    dDescFS:SetFont(GetFont(), 11)
    dDescFS:SetPoint("TOPLEFT", descContent, "TOPLEFT", 0, 0)
    dDescFS:SetWidth(CONTENT_W - 8)
    dDescFS:SetJustifyH("LEFT")
    dDescFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
    detail.descFS = dDescFS

    -- Bottom bar
    local bottomSep = MainFrame:CreateTexture(nil, "ARTWORK")
    bottomSep:SetTexture("Interface\\Buttons\\WHITE8X8")
    bottomSep:SetHeight(1)
    bottomSep:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", 6, BOTTOM_H)
    bottomSep:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -6, BOTTOM_H)
    bottomSep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])

    local costLabel = MainFrame:CreateFontString(nil, "OVERLAY")
    costLabel:SetFont(GetFont(), 11, "OUTLINE")
    costLabel:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", SIDE_PAD, 32)
    costLabel:SetJustifyH("LEFT")
    costLabel:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
    costLabel:SetText("花费:")
    MainFrame.costLabel = costLabel

    local costMoney = CreateFrame("Frame", "SFramesTrainerCostMoney", MainFrame, "SmallMoneyFrameTemplate")
    costMoney:SetPoint("LEFT", costLabel, "RIGHT", 4, 0)
    costMoney:SetWidth(120)
    costMoney:SetHeight(14)
    costMoney:SetFrameLevel(MainFrame:GetFrameLevel() + 5)
    costMoney:UnregisterAllEvents()
    costMoney:SetScript("OnEvent", nil)
    costMoney:SetScript("OnShow", nil)
    costMoney.moneyType = nil
    costMoney.small = 1
    MainFrame.costMoney = costMoney

    local availLabel = MainFrame:CreateFontString(nil, "OVERLAY")
    availLabel:SetFont(GetFont(), 11, "OUTLINE")
    availLabel:SetPoint("BOTTOMLEFT", MainFrame, "BOTTOMLEFT", SIDE_PAD, 16)
    availLabel:SetJustifyH("LEFT")
    availLabel:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
    availLabel:SetText("可用:")
    MainFrame.availLabel = availLabel

    local availMoney = CreateFrame("Frame", "SFramesTrainerAvailMoney", MainFrame, "SmallMoneyFrameTemplate")
    availMoney:SetPoint("LEFT", availLabel, "RIGHT", 4, 0)
    availMoney:SetWidth(120)
    availMoney:SetHeight(14)
    availMoney:SetFrameLevel(MainFrame:GetFrameLevel() + 5)
    availMoney:UnregisterAllEvents()
    availMoney:SetScript("OnEvent", nil)
    availMoney:SetScript("OnShow", nil)
    availMoney.moneyType = nil
    availMoney.small = 1
    MainFrame.availMoney = availMoney

    local trainBtn = CreateActionBtn(MainFrame, "训练", 80)
    trainBtn:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -(SIDE_PAD + 90), 8)
    trainBtn:SetScript("OnClick", function()
        if this.disabled then return end
        if IsControlKeyDown() and BuyTrainerService then
            local numServices = GetNumTrainerServices and GetNumTrainerServices() or 0
            local gold = GetMoney()
            for i = 1, numServices do
                local name, _, category = GetTrainerServiceInfo(i)
                if name and category == "available" and not IsServiceHeader(i) then
                    local ok, cost = pcall(GetTrainerServiceCost, i)
                    if ok and cost and gold >= cost then
                        pcall(BuyTrainerService, i)
                        gold = gold - cost
                    end
                end
            end
            return
        end
        if selectedIndex and BuyTrainerService then
            BuyTrainerService(selectedIndex)
        end
    end)
    trainBtn:SetScript("OnEnter", function()
        if not this.disabled then
            this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
            this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
            this.label:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3])
        end
        GameTooltip:SetOwner(this, "ANCHOR_TOP")
        GameTooltip:AddLine("Ctrl+点击: 学习所有可学技能", 0.8, 0.8, 0.8)
        GameTooltip:Show()
    end)
    trainBtn:SetScript("OnLeave", function()
        if not this.disabled then
            this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
            this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
            this.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
        end
        GameTooltip:Hide()
    end)
    MainFrame.trainBtn = trainBtn

    local exitBtn = CreateActionBtn(MainFrame, "退出", 80)
    exitBtn:SetPoint("BOTTOMRIGHT", MainFrame, "BOTTOMRIGHT", -SIDE_PAD, 8)
    exitBtn:SetScript("OnClick", function() MainFrame:Hide() end)

    -- Events
    local function CleanupBlizzardTrainer()
        if not ClassTrainerFrame then return end
        ClassTrainerFrame:SetScript("OnHide", function() end)
        if HideUIPanel then pcall(HideUIPanel, ClassTrainerFrame) end
        if ClassTrainerFrame:IsVisible() then ClassTrainerFrame:Hide() end
        ClassTrainerFrame:SetAlpha(0)
        ClassTrainerFrame:EnableMouse(false)
    end

    MainFrame:SetScript("OnHide", function()
        if CloseTrainer then pcall(CloseTrainer) end
        if CloseGossip then pcall(CloseGossip) end
        CleanupBlizzardTrainer()
    end)

    MainFrame:RegisterEvent("TRAINER_SHOW")
    MainFrame:RegisterEvent("TRAINER_UPDATE")
    MainFrame:RegisterEvent("TRAINER_CLOSED")
    MainFrame:SetScript("OnEvent", function()
        if event == "TRAINER_SHOW" then
            if ClassTrainerFrame then
                ClassTrainerFrame:SetScript("OnHide", function() end)
                ClassTrainerFrame:SetAlpha(0)
                ClassTrainerFrame:EnableMouse(false)
            end

            if SetTrainerServiceTypeFilter then
                SetTrainerServiceTypeFilter("available", 1)
                SetTrainerServiceTypeFilter("unavailable", 1)
                SetTrainerServiceTypeFilter("used", 1)
            end

            selectedIndex = nil
            currentFilter = "all"
            collapsedCats = {}
            qualityCache = {}
            local npcName = UnitName("npc") or "训练师"
            if IsTradeskillTrainer and IsTradeskillTrainer() then
                npcName = npcName .. " - 专业训练"
            end
            MainFrame.npcNameFS:SetText(npcName)
            MainFrame:Show()
            BuildDisplayList()
            for _, entry in ipairs(displayList) do
                if entry.type == "service" then
                    selectedIndex = entry.data.index
                    pcall(SelectTrainerService, entry.data.index)
                    break
                end
            end
            FullUpdate()

            MainFrame._hideBlizzTimer = 0
            MainFrame:SetScript("OnUpdate", function()
                if not this._hideBlizzTimer then return end
                this._hideBlizzTimer = this._hideBlizzTimer + arg1
                if this._hideBlizzTimer > 0.05 then
                    this._hideBlizzTimer = nil
                    this:SetScript("OnUpdate", nil)
                    CleanupBlizzardTrainer()
                end
            end)
        elseif event == "TRAINER_UPDATE" then
            if MainFrame:IsVisible() then FullUpdate() end
        elseif event == "TRAINER_CLOSED" then
            MainFrame._hideBlizzTimer = nil
            MainFrame:SetScript("OnUpdate", nil)
            CleanupBlizzardTrainer()
            MainFrame:Hide()
        end
    end)
    MainFrame:Hide()

    tinsert(UISpecialFrames, "SFramesTrainerFrame")
end

--------------------------------------------------------------------------------
-- Bootstrap
--------------------------------------------------------------------------------
local bootstrap = CreateFrame("Frame")
bootstrap:RegisterEvent("PLAYER_LOGIN")
bootstrap:RegisterEvent("ADDON_LOADED")
bootstrap:SetScript("OnEvent", function()
    if event == "PLAYER_LOGIN" then
        if SFramesDB.enableTrainer == nil then
            SFramesDB.enableTrainer = true
        end
        if SFramesDB.enableTrainer ~= false then
            TUI:Initialize()
        end
    elseif event == "ADDON_LOADED" and arg1 == "Blizzard_TrainerUI" then
        if MainFrame and ClassTrainerFrame then
            ClassTrainerFrame:SetScript("OnHide", function() end)
            ClassTrainerFrame:SetAlpha(0)
            ClassTrainerFrame:EnableMouse(false)
        end
    end
end)
