--------------------------------------------------------------------------------
-- Nanami-UI: Beast Training UI (BeastTrainingUI.lua)
-- Dedicated panel for Hunter pet beast training (训练野兽)
-- Separate from TradeSkillUI; shows training points, skill ranks, etc.
-- NOTE: Lua 5.0 upvalue limit = 32 per closure; all state packed into tables.
--------------------------------------------------------------------------------

SFrames = SFrames or {}
SFrames.BeastTrainingUI = {}
local BTUI = SFrames.BeastTrainingUI
SFramesDB = SFramesDB or {}

local BEAST_NAMES = {
    ["训练野兽"] = true,
    ["Beast Training"] = true,
}

function BTUI.IsBeastTraining()
    local name = GetCraftName and GetCraftName() or ""
    return BEAST_NAMES[name] == true
end

--------------------------------------------------------------------------------
-- Theme
--------------------------------------------------------------------------------
local T = SFrames.Theme:Extend({
    tpGood      = { 0.40, 0.90, 0.40 },
    tpLow       = { 0.90, 0.60, 0.20 },
    tpNone      = { 0.90, 0.30, 0.30 },
    rankText    = { 0.80, 0.70, 1.00 },
    available   = { 0.25, 1.00, 0.25 },
    unavailable = { 0.80, 0.20, 0.20 },
    learned     = { 0.50, 0.50, 0.50 },
})

--------------------------------------------------------------------------------
-- Layout (packed to save upvalues)
--------------------------------------------------------------------------------
local L = {
    FRAME_W     = 620,  FRAME_H     = 470,
    HEADER_H    = 52,   SIDE_PAD    = 10,
    FILTER_H    = 26,   LIST_ROW_H  = 28,
    CAT_ROW_H   = 20,   BOTTOM_H    = 44,
    SCROLL_STEP = 36,   SCROLLBAR_W = 12,
    MAX_ROWS    = 60,   LEFT_W      = 280,
}
L.RIGHT_W    = L.FRAME_W - L.LEFT_W
L.CONTENT_W  = L.RIGHT_W - L.SIDE_PAD * 2
L.LIST_ROW_W = L.LEFT_W - L.SIDE_PAD * 2 - L.SCROLLBAR_W - 4

--------------------------------------------------------------------------------
-- State (packed to save upvalues)
--------------------------------------------------------------------------------
local S = {
    MainFrame     = nil,
    selectedIndex = nil,
    currentFilter = "all",
    displayList   = {},
    rowButtons    = {},
    collapsedCats = {},
}

--------------------------------------------------------------------------------
-- Tooltip scanner for training point cost & requirements
--------------------------------------------------------------------------------
local scanTip = nil

function BTUI.GetCraftExtendedInfo(index)
    local name, rank, skillType, numAvail, _, _, tpCost = GetCraftInfo(index)
    return name, rank, skillType,
           tonumber(numAvail) or 0,
           tonumber(tpCost) or 0
end

function BTUI.GetSkillTooltipLines(index)
    if not scanTip then
        scanTip = CreateFrame("GameTooltip", "SFramesBTScanTip", nil, "GameTooltipTemplate")
    end
    scanTip:SetOwner(WorldFrame, "ANCHOR_NONE")
    scanTip:ClearLines()
    local ok = pcall(scanTip.SetCraftSpell, scanTip, index)
    if not ok then scanTip:Hide(); return {} end

    local lines = {}
    local numLines = scanTip:NumLines()
    for i = 2, numLines do
        local leftFS = _G["SFramesBTScanTipTextLeft" .. i]
        local rightFS = _G["SFramesBTScanTipTextRight" .. i]
        if leftFS then
            local lt = leftFS:GetText() or ""
            local rt = rightFS and rightFS:GetText() or ""
            local r, g, b = leftFS:GetTextColor()
            if lt ~= "" or rt ~= "" then
                table.insert(lines, {
                    left = lt, right = rt,
                    r = r, g = g, b = b,
                })
            end
        end
    end
    scanTip:Hide()
    return lines
end

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

function BTUI.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

function BTUI.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

--------------------------------------------------------------------------------
-- Widget Factories
--------------------------------------------------------------------------------
function BTUI.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(BTUI.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

function BTUI.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(BTUI.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

function BTUI.CreateListRow(parent, idx)
    local row = CreateFrame("Button", nil, parent)
    row:SetWidth(L.LIST_ROW_W); row:SetHeight(L.LIST_ROW_H)

    local iconFrame = CreateFrame("Frame", nil, row)
    iconFrame:SetWidth(L.LIST_ROW_H - 4); iconFrame:SetHeight(L.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 font = BTUI.GetFont()

    local tpFS = row:CreateFontString(nil, "OVERLAY")
    tpFS:SetFont(font, 10, "OUTLINE")
    tpFS:SetPoint("RIGHT", row, "RIGHT", -4, 0)
    tpFS:SetJustifyH("RIGHT")
    tpFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
    tpFS:Hide()
    row.tpFS = tpFS

    local nameFS = row:CreateFontString(nil, "OVERLAY")
    nameFS:SetFont(font, 11, "OUTLINE")
    nameFS:SetPoint("LEFT", iconFrame, "RIGHT", 5, 2)
    nameFS:SetPoint("RIGHT", tpFS, "LEFT", -4, 0)
    nameFS:SetJustifyH("LEFT")
    nameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
    row.nameFS = nameFS

    local rankFS = row:CreateFontString(nil, "OVERLAY")
    rankFS:SetFont(font, 9, "OUTLINE")
    rankFS:SetPoint("TOPLEFT", nameFS, "BOTTOMLEFT", 0, 0)
    rankFS:SetJustifyH("LEFT")
    rankFS:SetTextColor(T.rankText[1], T.rankText[2], T.rankText[3])
    row.rankFS = rankFS

    local catFS = row:CreateFontString(nil, "OVERLAY")
    catFS:SetFont(font, 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 selBg = row:CreateTexture(nil, "ARTWORK")
    selBg:SetTexture("Interface\\Buttons\\WHITE8X8"); selBg:SetAllPoints(row)
    selBg:SetVertexColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 0.40)
    selBg:Hide(); row.selBg = selBg

    local selGlow = row:CreateTexture(nil, "ARTWORK")
    selGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
    selGlow:SetWidth(4); selGlow:SetHeight(L.LIST_ROW_H)
    selGlow:SetPoint("LEFT", row, "LEFT", 0, 0)
    selGlow:SetVertexColor(1, 0.65, 0.85, 1)
    selGlow:Hide(); row.selGlow = selGlow

    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.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 0.8)
    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.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 0.8)
    selBot:Hide(); row.selBot = selBot

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

    row.craftIndex = nil; row.isHeader = false

    row:SetScript("OnEnter", function()
        if this.craftIndex and not this.isHeader then
            GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
            local ok2 = pcall(GameTooltip.SetCraftSpell, GameTooltip, this.craftIndex)
            if ok2 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(L.CAT_ROW_H)
        self.iconFrame:Hide(); self.nameFS:Hide(); self.rankFS:Hide(); self.tpFS:Hide()
        self.highlight:SetAlpha(0.15)
        self.selBg:Hide(); self.selGlow:Hide(); self.selTop:Hide(); self.selBot:Hide()
        self.catFS:SetText((collapsed and "+" or "-") .. "  " .. (name or ""))
        self.catFS:Show(); self.catSep:Show()
    end

    function row:SetAsSkill(skill)
        self.isHeader = false; self.craftIndex = skill.index
        self:SetHeight(L.LIST_ROW_H)
        self.iconFrame:Show(); self.nameFS:Show()
        self.catFS:Hide(); self.catSep:Hide(); self.highlight:SetAlpha(0.3)

        local iconTex = GetCraftIcon and GetCraftIcon(skill.index)
        self.icon:SetTexture(iconTex)
        self.nameFS:SetText(skill.name)

        if skill.rank and skill.rank ~= "" then
            self.rankFS:SetText(skill.rank)
            self.rankFS:Show()
        else
            self.rankFS:SetText("")
            self.rankFS:Hide()
        end

        local tpCost = skill.tpCost or 0
        local canLearn = (tpCost > 0)

        if canLearn then
            local remaining = BTUI.GetRemainingTP()
            if remaining >= tpCost then
                self.tpFS:SetTextColor(T.tpGood[1], T.tpGood[2], T.tpGood[3])
            else
                self.tpFS:SetTextColor(T.tpNone[1], T.tpNone[2], T.tpNone[3])
            end
            self.tpFS:SetText(tpCost .. " TP")
            self.tpFS:Show()
            self.nameFS:SetTextColor(T.available[1], T.available[2], T.available[3])
            self.icon:SetVertexColor(1, 1, 1)
        else
            self.tpFS:Hide()
            self.nameFS:SetTextColor(T.learned[1], T.learned[2], T.learned[3])
            self.icon:SetVertexColor(0.5, 0.5, 0.5)
        end
    end

    function row:Clear()
        self.craftIndex = nil; self.isHeader = false
        self.selBg:Hide(); self.selGlow:Hide(); self.selTop:Hide(); self.selBot:Hide()
        self.tpFS:Hide()
        self:Hide()
    end

    return row
end

--------------------------------------------------------------------------------
-- Logic
--------------------------------------------------------------------------------
function BTUI.BuildDisplayList()
    S.displayList = {}
    local numCrafts = GetNumCrafts and GetNumCrafts() or 0
    if numCrafts == 0 then return end

    local currentCat = nil
    local catSkills = {}
    local catOrder = {}

    for i = 1, numCrafts do
        local name, rank, skillType, numAvail, tpCost = BTUI.GetCraftExtendedInfo(i)
        if name then
            if skillType == "header" then
                currentCat = name
                if not catSkills[name] then
                    catSkills[name] = {}
                    table.insert(catOrder, name)
                end
            else
                if not currentCat then
                    currentCat = "技能"
                    if not catSkills[currentCat] then
                        catSkills[currentCat] = {}
                        table.insert(catOrder, currentCat)
                    end
                end
                local canLearn = (tpCost > 0)
                local show = true
                if S.currentFilter == "available" then
                    show = canLearn
                elseif S.currentFilter == "used" then
                    show = (not canLearn)
                end
                if show then
                    table.insert(catSkills[currentCat], {
                        index = i,
                        name = name,
                        rank = rank or "",
                        skillType = skillType or "none",
                        numAvail = numAvail,
                        tpCost = tpCost,
                    })
                end
            end
        end
    end

    local hasCats = table.getn(catOrder) > 1
    for _, catName in ipairs(catOrder) do
        local skills = catSkills[catName]
        if skills and table.getn(skills) > 0 then
            if hasCats then
                table.insert(S.displayList, {
                    type = "header", name = catName,
                    collapsed = S.collapsedCats[catName],
                })
            end
            if not S.collapsedCats[catName] then
                for _, skill in ipairs(skills) do
                    table.insert(S.displayList, { type = "skill", data = skill })
                end
            end
        end
    end
end

function BTUI.GetRemainingTP()
    if not GetPetTrainingPoints then return 0, 0 end
    local ok, total, spent = pcall(GetPetTrainingPoints)
    if not ok then return 0, 0 end
    total = total or 0
    spent = spent or 0
    return total - spent, total
end

function BTUI.UpdateTrainingPoints()
    if not S.MainFrame then return end
    local remaining, total = BTUI.GetRemainingTP()
    local color = T.tpGood
    if remaining <= 0 then color = T.tpNone
    elseif remaining < 10 then color = T.tpLow end
    S.MainFrame.tpValueFS:SetText(tostring(remaining))
    S.MainFrame.tpValueFS:SetTextColor(color[1], color[2], color[3])
end

function BTUI.UpdateList()
    if not S.MainFrame or not S.MainFrame:IsVisible() then return end
    BTUI.BuildDisplayList()
    local content = S.MainFrame.listScroll.content
    local count = table.getn(S.displayList)
    local y = 0
    for i = 1, L.MAX_ROWS do
        local row = S.rowButtons[i]
        if i <= count then
            local entry = S.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 + L.CAT_ROW_H
            else
                row:SetAsSkill(entry.data)
                row:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y)
                row.catName = nil
                row:Show(); y = y + L.LIST_ROW_H
                if S.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.selBg:Show(); row.selGlow:Show(); row.selTop:Show(); row.selBot:Show()
                    row.nameFS:SetTextColor(1, 1, 1)
                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.selBg:Hide(); row.selGlow:Hide(); row.selTop:Hide(); row.selBot:Hide()
                end
            end
        else row:Clear() end
    end
    content:SetHeight(math.max(1, y))
end

function BTUI.UpdateDetail()
    if not S.MainFrame then return end
    local detail = S.MainFrame.detail
    if not S.selectedIndex then
        detail.iconFrame:Hide()
        detail.nameFS:SetText(""); detail.rankFS:SetText("")
        detail.descFS:SetText(""); detail.costFS:SetText("")
        detail.reqFS:SetText("")
        S.MainFrame.trainBtn:SetDisabled(true)
        return
    end

    local name, rank, skillType, numAvail, tpCost = BTUI.GetCraftExtendedInfo(S.selectedIndex)
    local iconTex = GetCraftIcon and GetCraftIcon(S.selectedIndex)

    detail.icon:SetTexture(iconTex); detail.iconFrame:Show()
    detail.nameFS:SetText(name or "")

    local canLearn = (tpCost > 0)
    if canLearn then
        detail.nameFS:SetTextColor(T.available[1], T.available[2], T.available[3])
    else
        detail.nameFS:SetTextColor(T.learned[1], T.learned[2], T.learned[3])
    end

    if rank and rank ~= "" then
        detail.rankFS:SetText(rank)
        detail.rankFS:SetTextColor(T.rankText[1], T.rankText[2], T.rankText[3])
    else
        detail.rankFS:SetText("")
    end

    local desc = ""
    if GetCraftDescription then
        local ok, d = pcall(GetCraftDescription, S.selectedIndex)
        if ok and d then desc = d end
    end
    detail.descFS:SetText(desc)

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

    if tpCost > 0 then
        local remaining = BTUI.GetRemainingTP()
        local costColor = remaining >= tpCost and "|cff40ff40" or "|cffff4040"
        detail.costFS:SetText("训练点数: " .. costColor .. tpCost .. "|r  (剩余: " .. remaining .. ")")
    else
        detail.costFS:SetText("")
    end

    local lines = BTUI.GetSkillTooltipLines(S.selectedIndex)
    local reqParts = {}
    for _, line in ipairs(lines) do
        if line.r and line.r > 0.85 and line.g < 0.35 and line.b < 0.35 then
            table.insert(reqParts, "|cffff4040" .. line.left .. "|r")
        end
    end
    detail.reqFS:SetText(table.concat(reqParts, "\n"))

    S.MainFrame.trainBtn:SetDisabled(not canLearn)
end

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

function BTUI.FullUpdate()
    BTUI.UpdateTrainingPoints()
    BTUI.UpdateFilters()
    BTUI.UpdateList()
    BTUI.UpdateDetail()
    if BTUI.UpdateScrollbar then BTUI.UpdateScrollbar() end
end

function BTUI.SelectSkill(index)
    S.selectedIndex = index
    if SelectCraft then pcall(SelectCraft, index) end
    BTUI.FullUpdate()
end

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

--------------------------------------------------------------------------------
-- Hide Blizzard Craft Frame
--------------------------------------------------------------------------------
function BTUI.CleanupBlizzardCraft()
    if not CraftFrame then return end
    CraftFrame:SetScript("OnHide", function() end)
    if HideUIPanel then pcall(HideUIPanel, CraftFrame) end
    if CraftFrame:IsVisible() then CraftFrame:Hide() end
    CraftFrame:SetAlpha(0); CraftFrame:EnableMouse(false)
end

--------------------------------------------------------------------------------
-- Initialize
--------------------------------------------------------------------------------
function BTUI:Initialize()
    if S.MainFrame then return end
    local MF = CreateFrame("Frame", "SFramesBeastTrainingFrame", UIParent)
    S.MainFrame = MF
    MF:SetWidth(L.FRAME_W); MF:SetHeight(L.FRAME_H)
    MF:SetPoint("LEFT", UIParent, "LEFT", 36, 0)
    MF:SetFrameStrata("HIGH"); MF:SetToplevel(true); MF:EnableMouse(true)
    MF:SetMovable(true); MF:RegisterForDrag("LeftButton")
    MF:SetScript("OnDragStart", function() this:StartMoving() end)
    MF:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
    BTUI.SetRoundBackdrop(MF); BTUI.CreateShadow(MF)

    local font = BTUI.GetFont()

    -- ═══ Header ═════════════════════════════════════════════════════════
    local header = CreateFrame("Frame", nil, MF)
    header:SetPoint("TOPLEFT", 0, 0); header:SetPoint("TOPRIGHT", 0, 0)
    header:SetHeight(L.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, "dragon", 16)
    titleIco:SetDrawLayer("OVERLAY")
    titleIco:SetPoint("TOPLEFT", header, "TOPLEFT", L.SIDE_PAD, -6)
    titleIco:SetVertexColor(T.gold[1], T.gold[2], T.gold[3])

    local titleFS = header:CreateFontString(nil, "OVERLAY")
    titleFS:SetFont(font, 14, "OUTLINE")
    titleFS:SetPoint("LEFT", titleIco, "RIGHT", 5, 0)
    titleFS:SetJustifyH("LEFT")
    titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
    MF.titleFS = titleFS

    -- Training points in header
    local tpIco = SFrames:CreateIcon(header, "star", 12)
    tpIco:SetDrawLayer("OVERLAY")
    tpIco:SetPoint("BOTTOMLEFT", header, "BOTTOMLEFT", L.SIDE_PAD, 8)
    tpIco:SetVertexColor(0.55, 0.85, 0.4)

    local tpLabelFS = header:CreateFontString(nil, "OVERLAY")
    tpLabelFS:SetFont(font, 11, "OUTLINE")
    tpLabelFS:SetPoint("LEFT", tpIco, "RIGHT", 4, 0)
    tpLabelFS:SetText("训练点数:")
    tpLabelFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])

    local tpValueFS = header:CreateFontString(nil, "OVERLAY")
    tpValueFS:SetFont(font, 13, "OUTLINE")
    tpValueFS:SetPoint("LEFT", tpLabelFS, "RIGHT", 6, 0)
    tpValueFS:SetText("0")
    tpValueFS:SetTextColor(T.tpGood[1], T.tpGood[2], T.tpGood[3])
    MF.tpValueFS = tpValueFS

    -- Pet level in header
    local petLvlFS = header:CreateFontString(nil, "OVERLAY")
    petLvlFS:SetFont(font, 10, "OUTLINE")
    petLvlFS:SetPoint("LEFT", tpValueFS, "RIGHT", 16, 0)
    petLvlFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
    MF.petLvlFS = petLvlFS

    -- Close button
    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() S.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)

    -- Separators
    local hsep = MF:CreateTexture(nil, "ARTWORK")
    hsep:SetTexture("Interface\\Buttons\\WHITE8X8"); hsep:SetHeight(1)
    hsep:SetPoint("TOPLEFT", MF, "TOPLEFT", 4, -L.HEADER_H)
    hsep:SetPoint("TOPRIGHT", MF, "TOPRIGHT", -4, -L.HEADER_H)
    hsep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])

    local vdiv = MF:CreateTexture(nil, "ARTWORK")
    vdiv:SetTexture("Interface\\Buttons\\WHITE8X8"); vdiv:SetWidth(1)
    vdiv:SetPoint("TOP", MF, "TOPLEFT", L.LEFT_W, -(L.HEADER_H + 2))
    vdiv:SetPoint("BOTTOM", MF, "BOTTOMLEFT", L.LEFT_W, 4)
    vdiv:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])

    -- ═══ LEFT: Filters + Skill List ═════════════════════════════════════
    local fb = CreateFrame("Frame", nil, MF)
    fb:SetPoint("TOPLEFT", MF, "TOPLEFT", L.SIDE_PAD, -(L.HEADER_H + 4))
    fb:SetWidth(L.LEFT_W - L.SIDE_PAD * 2); fb:SetHeight(22)

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

    local fAvail = BTUI.CreateFilterBtn(fb, "可学", 52)
    fAvail:SetPoint("LEFT", fAll, "RIGHT", 3, 0)
    fAvail:SetScript("OnClick", function() S.currentFilter = "available"; BTUI.FullUpdate() end)
    MF.filterAvail = fAvail

    local fUsed = BTUI.CreateFilterBtn(fb, "已学", 52)
    fUsed:SetPoint("LEFT", fAvail, "RIGHT", 3, 0)
    fUsed:SetScript("OnClick", function() S.currentFilter = "used"; BTUI.FullUpdate() end)
    MF.filterUsed = fUsed

    -- List scroll area
    local listTop = L.HEADER_H + L.FILTER_H + 8
    local ls = CreateFrame("ScrollFrame", "SFramesBTListScroll", MF)
    ls:SetPoint("TOPLEFT", MF, "TOPLEFT", L.SIDE_PAD, -listTop)
    ls:SetPoint("BOTTOMRIGHT", MF, "BOTTOMLEFT", L.SIDE_PAD + L.LIST_ROW_W, 6)

    local lc = CreateFrame("Frame", "SFramesBTListContent", ls)
    lc:SetWidth(L.LIST_ROW_W); lc:SetHeight(1)
    ls:SetScrollChild(lc)
    ls:EnableMouseWheel(true)

    -- Scrollbar
    local sbTrack = CreateFrame("Frame", nil, MF)
    sbTrack:SetWidth(L.SCROLLBAR_W)
    sbTrack:SetPoint("TOPLEFT", ls, "TOPRIGHT", 2, 0)
    sbTrack:SetPoint("BOTTOMLEFT", ls, "BOTTOMRIGHT", 2, 0)
    sbTrack:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        tile = false, tileSize = 0, edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    sbTrack:SetBackdropColor(T.progressBg[1], T.progressBg[2], T.progressBg[3], 0.6)
    sbTrack:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], 0.3)

    local sbThumb = CreateFrame("Button", nil, sbTrack)
    sbThumb:SetWidth(L.SCROLLBAR_W - 2); sbThumb:SetHeight(30)
    sbThumb:SetPoint("TOP", sbTrack, "TOP", 0, -1)
    sbThumb:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        tile = false, tileSize = 0, edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    sbThumb:SetBackdropColor(T.progressFill[1], T.progressFill[2], T.progressFill[3], 0.7)
    sbThumb:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], 0.5)
    sbThumb:EnableMouse(true); sbThumb:SetMovable(true)
    sbThumb:RegisterForDrag("LeftButton")
    sbThumb._dragging = false

    sbThumb:SetScript("OnEnter", function()
        this:SetBackdropColor(T.progressFill[1], T.progressFill[2], T.progressFill[3], 1)
    end)
    sbThumb:SetScript("OnLeave", function()
        this:SetBackdropColor(T.progressFill[1], T.progressFill[2], T.progressFill[3], 0.7)
    end)
    sbThumb:SetScript("OnDragStart", function()
        this._dragging = true
        this._startY = select(2, GetCursorPosition()) / (this:GetEffectiveScale())
        this._startScroll = ls:GetVerticalScroll()
    end)
    sbThumb:SetScript("OnDragStop", function() this._dragging = false end)
    sbThumb:SetScript("OnUpdate", function()
        if not this._dragging then return end
        local cursorY = select(2, GetCursorPosition()) / (this:GetEffectiveScale())
        local delta = this._startY - cursorY
        local trackH = sbTrack:GetHeight() - this:GetHeight()
        if trackH <= 0 then return end
        local scrollMax = BTUI.GetScrollMax()
        local newScroll = this._startScroll + (delta / trackH) * scrollMax
        newScroll = math.max(0, math.min(scrollMax, newScroll))
        ls:SetVerticalScroll(newScroll)
        BTUI.UpdateScrollbar()
    end)

    sbTrack:EnableMouse(true)
    sbTrack:SetScript("OnMouseDown", function()
        local trackTop = sbTrack:GetTop()
        local cursorY = select(2, GetCursorPosition()) / (sbTrack:GetEffectiveScale())
        local clickRatio = (trackTop - cursorY) / sbTrack:GetHeight()
        clickRatio = math.max(0, math.min(1, clickRatio))
        ls:SetVerticalScroll(clickRatio * BTUI.GetScrollMax())
        BTUI.UpdateScrollbar()
    end)

    MF.sbTrack = sbTrack; MF.sbThumb = sbThumb

    function BTUI.GetScrollMax()
        local contentH = ls.content and ls.content:GetHeight() or 0
        local viewH = ls:GetHeight() or 0
        return math.max(0, contentH - viewH)
    end

    function BTUI.UpdateScrollbar()
        if not MF.sbThumb or not MF.sbTrack then return end
        local scrollMax = BTUI.GetScrollMax()
        if scrollMax <= 0 then MF.sbThumb:Hide(); return end
        MF.sbThumb:Show()
        local trackH = MF.sbTrack:GetHeight()
        local curScroll = ls:GetVerticalScroll()
        local ratio = curScroll / scrollMax
        ratio = math.max(0, math.min(1, ratio))
        local thumbH = math.max(20, trackH * (trackH / (trackH + scrollMax)))
        MF.sbThumb:SetHeight(thumbH)
        local maxOffset = trackH - thumbH - 2
        MF.sbThumb:ClearAllPoints()
        MF.sbThumb:SetPoint("TOP", MF.sbTrack, "TOP", 0, -(1 + ratio * maxOffset))
    end

    ls:SetScript("OnMouseWheel", function()
        local cur = this:GetVerticalScroll()
        local mx = BTUI.GetScrollMax()
        if arg1 > 0 then this:SetVerticalScroll(math.max(0, cur - L.SCROLL_STEP))
        else this:SetVerticalScroll(math.min(mx, cur + L.SCROLL_STEP)) end
        BTUI.UpdateScrollbar()
    end)
    ls:SetScript("OnScrollRangeChanged", function() BTUI.UpdateScrollbar() end)
    ls.content = lc; MF.listScroll = ls

    for i = 1, L.MAX_ROWS do
        local row = BTUI.CreateListRow(lc, i)
        row:SetWidth(L.LIST_ROW_W)
        row:EnableMouseWheel(true)
        row:SetScript("OnMouseWheel", function()
            local sf = S.MainFrame.listScroll
            local cur = sf:GetVerticalScroll()
            local mx = BTUI.GetScrollMax()
            if arg1 > 0 then sf:SetVerticalScroll(math.max(0, cur - L.SCROLL_STEP))
            else sf:SetVerticalScroll(math.min(mx, cur + L.SCROLL_STEP)) end
            BTUI.UpdateScrollbar()
        end)
        row:SetScript("OnClick", function()
            if this.isHeader and this.catName then
                BTUI.ToggleCategory(this.catName)
            elseif this.craftIndex then
                BTUI.SelectSkill(this.craftIndex)
            end
        end)
        S.rowButtons[i] = row
    end

    -- ═══ RIGHT: Detail ══════════════════════════════════════════════════
    local rightX = L.LEFT_W + L.SIDE_PAD
    local det = CreateFrame("Frame", nil, MF)
    det:SetPoint("TOPLEFT", MF, "TOPLEFT", rightX, -(L.HEADER_H + 6))
    det:SetPoint("BOTTOMRIGHT", MF, "BOTTOMRIGHT", -L.SIDE_PAD, L.BOTTOM_H + 2)
    MF.detail = det

    -- Icon
    local dIF = CreateFrame("Frame", nil, det)
    dIF:SetWidth(44); dIF:SetHeight(44); dIF:SetPoint("TOPLEFT", det, "TOPLEFT", 0, 0)
    dIF: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 },
    })
    dIF:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
    dIF:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
    dIF:Hide(); det.iconFrame = dIF

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

    dIF:EnableMouse(true)
    dIF:SetScript("OnEnter", function()
        if S.selectedIndex then
            GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
            local ok2 = pcall(GameTooltip.SetCraftSpell, GameTooltip, S.selectedIndex)
            if ok2 then GameTooltip:Show() else GameTooltip:Hide() end
        end
    end)
    dIF:SetScript("OnLeave", function() GameTooltip:Hide() end)

    -- Name
    local dName = det:CreateFontString(nil, "OVERLAY")
    dName:SetFont(font, 14, "OUTLINE")
    dName:SetPoint("TOPLEFT", dIF, "TOPRIGHT", 8, -2)
    dName:SetPoint("RIGHT", det, "RIGHT", -4, 0)
    dName:SetJustifyH("LEFT")
    dName:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
    det.nameFS = dName

    -- Rank
    local dRank = det:CreateFontString(nil, "OVERLAY")
    dRank:SetFont(font, 12, "OUTLINE")
    dRank:SetPoint("TOPLEFT", dName, "BOTTOMLEFT", 0, -2)
    dRank:SetJustifyH("LEFT")
    dRank:SetTextColor(T.rankText[1], T.rankText[2], T.rankText[3])
    det.rankFS = dRank

    -- Separator
    local sep1 = det:CreateTexture(nil, "ARTWORK")
    sep1:SetTexture("Interface\\Buttons\\WHITE8X8"); sep1:SetHeight(1)
    sep1:SetPoint("TOPLEFT", det, "TOPLEFT", 0, -54)
    sep1:SetPoint("RIGHT", det, "RIGHT", 0, 0)
    sep1:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], 0.3)

    -- Description (scrollable)
    local descScroll = CreateFrame("ScrollFrame", nil, det)
    descScroll:SetPoint("TOPLEFT", det, "TOPLEFT", 0, -60)
    descScroll:SetPoint("RIGHT", det, "RIGHT", -4, 0)
    descScroll:SetHeight(120)
    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)
    det.descScroll = descScroll

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

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

    -- Separator 2
    local sep2 = det:CreateTexture(nil, "ARTWORK")
    sep2:SetTexture("Interface\\Buttons\\WHITE8X8"); sep2:SetHeight(1)
    sep2:SetPoint("TOPLEFT", det, "TOPLEFT", 0, -186)
    sep2:SetPoint("RIGHT", det, "RIGHT", 0, 0)
    sep2:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], 0.3)

    -- TP Cost
    local dCost = det:CreateFontString(nil, "OVERLAY")
    dCost:SetFont(font, 12, "OUTLINE")
    dCost:SetPoint("TOPLEFT", det, "TOPLEFT", 2, -194)
    dCost:SetPoint("RIGHT", det, "RIGHT", -4, 0)
    dCost:SetJustifyH("LEFT")
    dCost:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
    det.costFS = dCost

    -- Requirements
    local dReq = det:CreateFontString(nil, "OVERLAY")
    dReq:SetFont(font, 11, "OUTLINE")
    dReq:SetPoint("TOPLEFT", dCost, "BOTTOMLEFT", 0, -4)
    dReq:SetPoint("RIGHT", det, "RIGHT", -4, 0)
    dReq:SetJustifyH("LEFT")
    det.reqFS = dReq

    -- ═══ Bottom Bar ═════════════════════════════════════════════════════
    local bsep = MF:CreateTexture(nil, "ARTWORK")
    bsep:SetTexture("Interface\\Buttons\\WHITE8X8"); bsep:SetHeight(1)
    bsep:SetPoint("BOTTOMLEFT", MF, "BOTTOMLEFT", 4, L.BOTTOM_H)
    bsep:SetPoint("BOTTOMRIGHT", MF, "BOTTOMRIGHT", -4, L.BOTTOM_H)
    bsep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])

    local trainBtn = BTUI.CreateActionBtn(MF, "训练", 100)
    trainBtn:SetPoint("BOTTOMRIGHT", MF, "BOTTOMRIGHT", -L.SIDE_PAD, 8)
    trainBtn:SetScript("OnClick", function()
        if this.disabled then return end
        if S.selectedIndex and DoCraft then
            DoCraft(S.selectedIndex)
        end
    end)
    MF.trainBtn = trainBtn

    local closeB = BTUI.CreateActionBtn(MF, "关闭", 80)
    closeB:SetPoint("BOTTOMRIGHT", trainBtn, "BOTTOMLEFT", -6, 0)
    closeB:SetScript("OnClick", function() S.MainFrame:Hide() end)

    -- ═══ Events ═════════════════════════════════════════════════════════
    MF:SetScript("OnHide", function()
        if CloseCraft then pcall(CloseCraft) end
        BTUI.CleanupBlizzardCraft()
    end)

    MF:RegisterEvent("CRAFT_SHOW")
    MF:RegisterEvent("CRAFT_UPDATE")
    MF:RegisterEvent("CRAFT_CLOSE")
    MF:SetScript("OnEvent", function()
        if event == "CRAFT_SHOW" then
            if not BTUI.IsBeastTraining() then return end
            if CraftFrame then
                CraftFrame:SetScript("OnHide", function() end)
                CraftFrame:SetAlpha(0); CraftFrame:EnableMouse(false)
            end
            S.selectedIndex = nil; S.currentFilter = "all"; S.collapsedCats = {}
            local petName = UnitName("pet") or ""
            if petName ~= "" then
                MF.titleFS:SetText("训练野兽 - " .. petName)
            else
                MF.titleFS:SetText("训练野兽")
            end
            local petLvl = UnitLevel("pet")
            if petLvl and petLvl > 0 then
                MF.petLvlFS:SetText("宠物等级: " .. petLvl)
            else
                MF.petLvlFS:SetText("")
            end
            MF:Show()
            BTUI.BuildDisplayList()
            for _, entry in ipairs(S.displayList) do
                if entry.type == "skill" then
                    BTUI.SelectSkill(entry.data.index); break
                end
            end
            BTUI.FullUpdate()
            MF._hideBlizzTimer = 0
            MF: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)
                    BTUI.CleanupBlizzardCraft()
                end
            end)
        elseif event == "CRAFT_UPDATE" then
            if S.MainFrame:IsVisible() then
                BTUI.FullUpdate()
            end
        elseif event == "CRAFT_CLOSE" then
            if S.MainFrame:IsVisible() then
                BTUI.CleanupBlizzardCraft()
                S.MainFrame._hideBlizzTimer = nil
                S.MainFrame:SetScript("OnUpdate", nil)
                S.MainFrame:Hide()
            end
        end
    end)

    MF:Hide()
    tinsert(UISpecialFrames, "SFramesBeastTrainingFrame")
end

--------------------------------------------------------------------------------
-- Bootstrap
--------------------------------------------------------------------------------
local bootstrap = CreateFrame("Frame")
bootstrap:RegisterEvent("PLAYER_LOGIN")
bootstrap:SetScript("OnEvent", function()
    if event == "PLAYER_LOGIN" then
        if SFramesDB.enableTradeSkill == nil then SFramesDB.enableTradeSkill = true end
        if SFramesDB.enableTradeSkill ~= false then BTUI:Initialize() end
    end
end)

SLASH_BTDEBUG1 = "/btdebug"
SlashCmdList["BTDEBUG"] = function()
    local p = "|cffff80ff[BT-Debug]|r "
    local numCrafts = GetNumCrafts and GetNumCrafts() or 0
    DEFAULT_CHAT_FRAME:AddMessage(p .. "Total crafts: " .. numCrafts)
    local remaining = BTUI.GetRemainingTP()
    DEFAULT_CHAT_FRAME:AddMessage(p .. "Remaining TP: " .. remaining)
    local shown = 0
    for i = 1, numCrafts do
        local v1,v2,v3,v4,v5,v6,v7 = GetCraftInfo(i)
        if v1 and v3 ~= "header" then
            shown = shown + 1
            if shown <= 12 then
                DEFAULT_CHAT_FRAME:AddMessage(p .. i .. ": " .. tostring(v1)
                    .. " " .. tostring(v2) .. " type=" .. tostring(v3)
                    .. " avail=" .. tostring(v4) .. " tp=" .. tostring(v7))
            end
        end
    end
    DEFAULT_CHAT_FRAME:AddMessage(p .. "Total skills: " .. shown)
end
