--------------------------------------------------------------------------------
-- Nanami-UI: Inspect Panel (InspectPanel.lua)
-- Equipment-focused inspect panel with modern rounded UI
-- Talent viewing delegates to native InspectFrame
--------------------------------------------------------------------------------

SFrames.InspectPanel = {}

local IP = SFrames.InspectPanel
local ipanel, equipPage
local inspectUnit = nil

--------------------------------------------------------------------------------
-- Theme (matches CharacterPanel)
--------------------------------------------------------------------------------
local T = SFrames.ActiveTheme

local FRAME_W = 340
local FRAME_H = 380
local HEADER_H = 28
local CONTENT_TOP = -(HEADER_H + 2)
local SIDE_PAD = 8
local INNER_PAD = 4
local SLOT_SIZE = 32
local SLOT_GAP = 2

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 SLOT_LABEL = {
    HeadSlot = "头部", NeckSlot = "颈部", ShoulderSlot = "肩部",
    BackSlot = "背部", ChestSlot = "胸部", ShirtSlot = "衬衣",
    WristSlot = "手腕", HandsSlot = "手套", WaistSlot = "腰带",
    LegsSlot = "腿部", FeetSlot = "脚部",
    Finger0Slot = "戒指1", Finger1Slot = "戒指2",
    Trinket0Slot = "饰品1", Trinket1Slot = "饰品2",
    MainHandSlot = "主手", SecondaryHandSlot = "副手",
    RangedSlot = "远程", AmmoSlot = "弹药", TabardSlot = "战袍",
}

local EQUIP_SLOTS_LEFT = {
    { id = 1, name = "HeadSlot" }, { id = 2, name = "NeckSlot" },
    { id = 3, name = "ShoulderSlot" }, { id = 15, name = "BackSlot" },
    { id = 5, name = "ChestSlot" }, { id = 4, name = "ShirtSlot" },
    { id = 19, name = "TabardSlot" }, { id = 9, name = "WristSlot" },
}
local EQUIP_SLOTS_RIGHT = {
    { id = 10, name = "HandsSlot" }, { id = 6, name = "WaistSlot" },
    { id = 7, name = "LegsSlot" }, { id = 8, name = "FeetSlot" },
    { id = 11, name = "Finger0Slot" }, { id = 12, name = "Finger1Slot" },
    { id = 13, name = "Trinket0Slot" }, { id = 14, name = "Trinket1Slot" },
}
local EQUIP_SLOTS_BOTTOM = {
    { id = 16, name = "MainHandSlot" }, { id = 17, name = "SecondaryHandSlot" },
    { id = 18, name = "RangedSlot" }, { id = 0, name = "AmmoSlot" },
}

local widgetId = 0

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

local function SetRoundBackdrop(frame, bgColor, borderColor)
    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 }
    })
    local bg = bgColor or T.bg
    local bd = borderColor or T.border
    frame:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 1)
    frame:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4] or 1)
end

local function SetPixelBackdrop(frame, bgColor, borderColor)
    frame:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        tile = false, tileSize = 0, edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 }
    })
    if bgColor then frame:SetBackdropColor(bgColor[1], bgColor[2], bgColor[3], bgColor[4] or 1) end
    if borderColor then frame:SetBackdropBorderColor(borderColor[1], borderColor[2], borderColor[3], borderColor[4] or 1) end
end

local function CreateShadow(parent, size)
    local s = CreateFrame("Frame", nil, parent)
    local sz = size or 4
    s:SetPoint("TOPLEFT", parent, "TOPLEFT", -sz, sz)
    s:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", sz, -sz)
    s:SetFrameLevel(math.max(parent:GetFrameLevel() - 1, 0))
    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.6)
    s:SetBackdropBorderColor(0, 0, 0, 0.45)
    return s
end

local function MakeSep(parent, x1, y1, x2, y2)
    local sep = parent:CreateTexture(nil, "ARTWORK")
    sep:SetTexture("Interface\\Buttons\\WHITE8X8")
    sep:SetVertexColor(T.sepColor[1], T.sepColor[2], T.sepColor[3], T.sepColor[4])
    sep:SetHeight(1)
    sep:SetPoint("TOPLEFT", parent, "TOPLEFT", x1, y1)
    sep:SetPoint("TOPRIGHT", parent, "TOPRIGHT", x2, y1)
    return sep
end

local function MakeFS(parent, size, justifyH, color)
    local fs = parent:CreateFontString(nil, "OVERLAY")
    fs:SetFont(GetFont(), size or 11, "OUTLINE")
    fs:SetJustifyH(justifyH or "LEFT")
    local c = color or T.valueText
    fs:SetTextColor(c[1], c[2], c[3])
    return fs
end

local function NextName(p)
    widgetId = widgetId + 1
    return "SFramesIP" .. (p or "") .. tostring(widgetId)
end

local function GetItemQualityFromLink(link)
    if not link then return nil end
    local _, _, q = string.find(link, "|c(%x+)|H")
    if not q then return nil end
    local m = {
        ["ff9d9d9d"] = 0, ["ffffffff"] = 1, ["ff1eff00"] = 2,
        ["ff0070dd"] = 3, ["ffa335ee"] = 4, ["ffff8000"] = 5,
    }
    return m[q]
end

local function GetInspectUnit()
    return inspectUnit or "target"
end

--------------------------------------------------------------------------------
-- Scroll helper
--------------------------------------------------------------------------------
local function CreateScrollFrame(parent, width, height)
    local holder = CreateFrame("Frame", NextName("SH"), parent)
    holder:SetWidth(width)
    holder:SetHeight(height)

    local scroll = CreateFrame("ScrollFrame", NextName("SF"), holder)
    scroll:SetPoint("TOPLEFT", holder, "TOPLEFT", 0, 0)
    scroll:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", -10, 0)

    local child = CreateFrame("Frame", NextName("SC"), scroll)
    child:SetWidth(width - 14)
    child:SetHeight(1)
    scroll:SetScrollChild(child)

    local slider = CreateFrame("Slider", NextName("SB"), holder)
    slider:SetWidth(6)
    slider:SetPoint("TOPRIGHT", holder, "TOPRIGHT", -1, -2)
    slider:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", -1, 2)
    slider:SetOrientation("VERTICAL")
    slider:SetMinMaxValues(0, 1)
    slider:SetValue(0)
    SetPixelBackdrop(slider, { 0.1, 0.1, 0.12, 0.4 }, { 0.15, 0.15, 0.18, 0.3 })

    local thumb = slider:CreateTexture(nil, "OVERLAY")
    thumb:SetTexture("Interface\\Buttons\\WHITE8X8")
    thumb:SetVertexColor(0.4, 0.4, 0.48, 0.7)
    thumb:SetWidth(6)
    thumb:SetHeight(28)
    slider:SetThumbTexture(thumb)

    slider:SetScript("OnValueChanged", function()
        scroll:SetVerticalScroll(this:GetValue())
    end)
    scroll:EnableMouseWheel(1)
    scroll:SetScript("OnMouseWheel", function()
        local cur = slider:GetValue()
        local step = 28
        local _, mx = slider:GetMinMaxValues()
        if arg1 > 0 then
            slider:SetValue(math.max(cur - step, 0))
        else
            slider:SetValue(math.min(cur + step, mx))
        end
    end)

    holder.scroll = scroll
    holder.child = child
    holder.slider = slider
    holder.SetContentHeight = function(self, h)
        child:SetHeight(h)
        local visH = scroll:GetHeight()
        local maxS = math.max(h - visH, 0)
        slider:SetMinMaxValues(0, maxS)
        if maxS == 0 then slider:Hide() else slider:Show() end
        slider:SetValue(math.min(slider:GetValue(), maxS))
    end
    return holder
end

--------------------------------------------------------------------------------
-- Main Frame (no tabs, single equipment view)
--------------------------------------------------------------------------------
local function CreateMainFrame()
    if ipanel then return ipanel end

    local f = CreateFrame("Frame", "SFramesInspectPanel", UIParent)
    f:SetWidth(FRAME_W)
    f:SetHeight(FRAME_H)
    f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
    f:SetFrameStrata("HIGH")
    f:EnableMouse(true)
    f:SetMovable(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", function() this:StartMoving() end)
    f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
    f:SetClampedToScreen(true)
    SetRoundBackdrop(f, T.bg, T.border)
    f.shadow = CreateShadow(f, 4)
    f:Hide()
    f:SetScript("OnHide", function()
        if not IP.switchingToNative then
            if ClearInspectPlayer then ClearInspectPlayer() end
            inspectUnit = nil
        end
        IP:HideSummary()
    end)

    table.insert(UISpecialFrames, "SFramesInspectPanel")

    f.classIcon = SFrames:CreateClassIcon(f, 16)
    f.classIcon.overlay:SetPoint("TOPLEFT", f, "TOPLEFT", SIDE_PAD + 2, -6)

    f.titleText = MakeFS(f, 12, "LEFT", T.gold)
    f.titleText:SetPoint("LEFT", f.classIcon.overlay, "RIGHT", 4, 0)

    f.subtitleText = MakeFS(f, 9, "LEFT", T.dimText)
    f.subtitleText:SetPoint("LEFT", f.titleText, "RIGHT", 6, 0)

    local function MakeHeaderBtn(parent, label, tooltip, offsetX, onClick)
        local btn = CreateFrame("Button", nil, parent)
        btn:SetWidth(46)
        btn:SetHeight(16)
        btn:SetPoint("TOPRIGHT", parent, "TOPRIGHT", offsetX, -6)
        btn:SetFrameLevel(parent:GetFrameLevel() + 3)
        SetRoundBackdrop(btn, T.btnBg, T.btnBorder)
        local txt = MakeFS(btn, 9, "CENTER", T.dimText)
        txt:SetPoint("CENTER", btn, "CENTER", 0, 0)
        txt:SetText(label)
        btn:SetScript("OnClick", onClick)
        btn:SetScript("OnEnter", function()
            this:SetBackdropColor(T.btnHover[1], T.btnHover[2], T.btnHover[3], T.btnHover[4])
            GameTooltip:SetOwner(this, "ANCHOR_BOTTOM")
            GameTooltip:AddLine(tooltip, 1, 1, 1)
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function()
            this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
            GameTooltip:Hide()
        end)
        return btn
    end

    MakeHeaderBtn(f, "荣誉", "打开原生荣誉面板", -77, function() IP:OpenNativeTab("honor") end)
    MakeHeaderBtn(f, "天赋", "打开原生天赋面板", -27, function() IP:OpenNativeTab("talent") end)

    local closeBtn = CreateFrame("Button", nil, f)
    closeBtn:SetWidth(16)
    closeBtn:SetHeight(16)
    closeBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -7, -6)
    closeBtn:SetFrameLevel(f:GetFrameLevel() + 3)
    SetRoundBackdrop(closeBtn, T.buttonDownBg, T.btnBorder)
    local closeTxt = MakeFS(closeBtn, 9, "CENTER", T.title)
    closeTxt:SetPoint("CENTER", closeBtn, "CENTER", 0, 0)
    closeTxt:SetText("x")
    closeBtn:SetScript("OnClick", function() f:Hide() end)
    closeBtn: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)
    closeBtn:SetScript("OnLeave", function()
        this:SetBackdropColor(T.buttonDownBg[1], T.buttonDownBg[2], T.buttonDownBg[3], T.buttonDownBg[4])
        this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
    end)

    MakeSep(f, 6, -HEADER_H, -6, -HEADER_H)

    equipPage = CreateFrame("Frame", NextName("Page"), f)
    equipPage:SetPoint("TOPLEFT", f, "TOPLEFT", 4, CONTENT_TOP)
    equipPage:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -4, 4)

    ipanel = f
    return f
end

--------------------------------------------------------------------------------
-- Title
--------------------------------------------------------------------------------
function IP:UpdateTitle()
    if not ipanel then return end
    local unit = GetInspectUnit()
    local name = UnitName(unit) or "未知"
    local level = UnitLevel(unit) or "?"
    local classLocal, classEn = UnitClass(unit)
    classLocal = classLocal or ""
    classEn = classEn or ""
    local raceLocal = UnitRace(unit) or ""

    local cc = SFrames.Config and SFrames.Config.colors and SFrames.Config.colors.class and SFrames.Config.colors.class[classEn]

    if equipPage and equipPage.modelNameText then
        if cc then
            equipPage.modelNameText:SetTextColor(cc.r, cc.g, cc.b)
        else
            equipPage.modelNameText:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
        end
        equipPage.modelNameText:SetText(name)
    end

    if equipPage and equipPage.modelGuildText then
        local guildName = GetGuildInfo and GetGuildInfo(unit) or nil
        if guildName and guildName ~= "" then
            equipPage.modelGuildText:SetText("<" .. guildName .. ">")
        else
            equipPage.modelGuildText:SetText("")
        end
    end

    local parts = {}
    table.insert(parts, "Lv." .. tostring(level))
    table.insert(parts, raceLocal .. classLocal)

    if equipPage and equipPage.avgIlvl then
        table.insert(parts, string.format("iLvl:%.1f", equipPage.avgIlvl))
    end

    if cc then
        ipanel.titleText:SetTextColor(cc.r, cc.g, cc.b)
    else
        ipanel.titleText:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
    end
    ipanel.titleText:SetText(table.concat(parts, " "))
    ipanel.subtitleText:SetText("")

    if ipanel.classIcon then
        SFrames:SetClassIcon(ipanel.classIcon, classEn)
    end
end

--------------------------------------------------------------------------------
-- Equipment Slot creation
--------------------------------------------------------------------------------
function IP:CreateEquipSlot(parent, slotID, slotName)
    local frame = CreateFrame("Button", NextName("Slot"), parent)
    frame:SetWidth(SLOT_SIZE)
    frame:SetHeight(SLOT_SIZE)
    SetRoundBackdrop(frame, T.slotBg, T.slotBorder)

    local icon = frame:CreateTexture(nil, "ARTWORK")
    icon:SetPoint("TOPLEFT", frame, "TOPLEFT", 4, -4)
    icon:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -4, 4)
    icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
    frame.icon = icon

    local ilvlText = frame:CreateFontString(nil, "OVERLAY")
    ilvlText:SetFont(GetFont(), 8, "OUTLINE")
    ilvlText:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -2, 2)
    ilvlText:SetTextColor(1, 0.82, 0)
    ilvlText:SetText("")
    frame.ilvlText = ilvlText

    local glow = frame:CreateTexture(nil, "OVERLAY")
    glow:SetTexture("Interface\\Buttons\\UI-ActionButton-Border")
    glow:SetBlendMode("ADD")
    glow:SetAlpha(0.8)
    glow:SetWidth(SLOT_SIZE * 2)
    glow:SetHeight(SLOT_SIZE * 2)
    glow:SetPoint("CENTER", frame, "CENTER", 0, 0)
    glow:Hide()
    frame.qualGlow = glow

    frame.slotID = slotID
    frame.slotName = slotName
    local _, emptyTex = GetInventorySlotInfo(slotName)
    frame.emptyTexture = emptyTex

    frame:SetScript("OnEnter", function()
        this:SetBackdropBorderColor(T.slotHover[1], T.slotHover[2], T.slotHover[3], T.slotHover[4])
        GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
        local unit = GetInspectUnit()
        if GetInventoryItemLink(unit, this.slotID) then
            GameTooltip:SetInventoryItem(unit, this.slotID)
        else
            local label = SLOT_LABEL[this.slotName] or this.slotName
            GameTooltip:AddLine(label .. " - 未装备", 0.5, 0.5, 0.5)
        end
        GameTooltip:Show()
    end)

    frame:SetScript("OnLeave", function()
        this:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
        GameTooltip:Hide()
    end)

    frame:SetScript("OnClick", function()
        if IsShiftKeyDown() then
            local unit = GetInspectUnit()
            local link = GetInventoryItemLink(unit, this.slotID)
            if link and ChatFrameEditBox and ChatFrameEditBox:IsVisible() then
                ChatFrameEditBox:Insert(link)
            elseif link then
                if DEFAULT_CHAT_FRAME then
                    DEFAULT_CHAT_FRAME:AddMessage(link)
                end
            end
        end
    end)

    return frame
end

--------------------------------------------------------------------------------
-- Equipment Page
--------------------------------------------------------------------------------
function IP:BuildEquipmentPage()
    if not equipPage or equipPage.built then return end
    equipPage.built = true

    local CONTENT_W = FRAME_W - INNER_PAD * 2
    local SCROLL_W = CONTENT_W - 10
    local contentH = FRAME_H - HEADER_H - 2 - INNER_PAD - 4
    local scrollArea = CreateScrollFrame(equipPage, CONTENT_W, contentH)
    scrollArea:SetPoint("TOPLEFT", equipPage, "TOPLEFT", 0, 0)
    equipPage.scrollArea = scrollArea

    local child = scrollArea.child
    local cw = SCROLL_W

    local slotColW = SLOT_SIZE + 6
    local modelW = cw - slotColW * 2 - 26
    if modelW < 80 then modelW = 80 end
    local modelH = 8 * (SLOT_SIZE + SLOT_GAP) - SLOT_GAP

    local modelBg = CreateFrame("Frame", nil, child)
    modelBg:SetWidth(modelW)
    modelBg:SetHeight(modelH)
    modelBg:SetPoint("TOP", child, "TOP", 0, -4)
    SetRoundBackdrop(modelBg, T.modelBg, T.modelBorder)
    equipPage.modelBgFrame = modelBg

    local modelFrame = CreateFrame("Frame", nil, equipPage)
    modelFrame:SetWidth(modelW - 8)
    modelFrame:SetHeight(modelH - 24)
    modelFrame:SetPoint("TOP", modelBg, "TOP", 0, -4)
    modelFrame:SetFrameLevel(equipPage:GetFrameLevel() + 5)

    local model = CreateFrame("PlayerModel", NextName("Model"), modelFrame)
    model:SetAllPoints(modelFrame)
    equipPage.model = model
    equipPage.modelFrame = modelFrame

    model:EnableMouse(true)
    model:EnableMouseWheel(1)
    model.rotating = false
    model.panning = false
    model.curFacing = 0.4
    model.curScale = 1.0
    model.posX = 0
    model.posY = 0

    model:SetScript("OnMouseDown", function()
        if arg1 == "LeftButton" then
            this.rotating = true
            this.startX = GetCursorPosition()
            this.startFacing = this.curFacing or 0
        elseif arg1 == "RightButton" then
            this.panning = true
            local cx, cy = GetCursorPosition()
            this.panStartX = cx
            this.panStartY = cy
            this.panOriginX = this.posX or 0
            this.panOriginY = this.posY or 0
        end
    end)
    model:SetScript("OnMouseUp", function()
        if arg1 == "LeftButton" then
            this.rotating = false
        elseif arg1 == "RightButton" then
            this.panning = false
        end
    end)
    model:SetScript("OnMouseWheel", function()
        local step = 0.1
        local ns = (this.curScale or 1) + arg1 * step
        if ns < 0.2 then ns = 0.2 end
        if ns > 4.0 then ns = 4.0 end
        this.curScale = ns
        this:SetModelScale(ns)
    end)
    model:SetScript("OnUpdate", function()
        if this.rotating then
            local cx = GetCursorPosition()
            local diff = (cx - (this.startX or cx)) * 0.01
            local nf = (this.startFacing or 0) + diff
            this.curFacing = nf
            this:SetFacing(nf)
        elseif this.panning then
            local cx, cy = GetCursorPosition()
            local es = this:GetEffectiveScale()
            if es < 0.01 then es = 1 end
            local dx = (cx - (this.panStartX or cx)) / (es * 35)
            local dy = (cy - (this.panStartY or cy)) / (es * 35)
            this.posX = (this.panOriginX or 0) + dx
            this.posY = (this.panOriginY or 0) + dy
            this:SetPosition(this.posY, 0, this.posX)
        elseif this.autoRotateDir then
            local speed = 1.5 * (arg1 or 0.016)
            this.curFacing = (this.curFacing or 0) + speed * this.autoRotateDir
            this:SetFacing(this.curFacing)
        end
    end)
    model.ResetView = function(self)
        self.curFacing = 0.4
        self.curScale = 1.0
        self.posX = 0
        self.posY = 0
        self:SetFacing(0.4)
        self:SetModelScale(1.0)
        self:SetPosition(0, 0, 0)
    end

    -- Name/guild overlay floats on top of 3D model
    local nameOverlay = CreateFrame("Frame", nil, equipPage)
    nameOverlay:SetWidth(modelW)
    nameOverlay:SetHeight(30)
    nameOverlay:SetPoint("TOP", modelBg, "TOP", 0, -4)
    nameOverlay:SetFrameLevel(equipPage:GetFrameLevel() + 7)
    nameOverlay:EnableMouse(false)

    local modelNameText = nameOverlay:CreateFontString(nil, "OVERLAY")
    modelNameText:SetFont(GetFont(), 11, "OUTLINE")
    modelNameText:SetPoint("TOP", nameOverlay, "TOP", 0, -5)
    modelNameText:SetJustifyH("CENTER")
    modelNameText:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
    equipPage.modelNameText = modelNameText

    local modelGuildText = nameOverlay:CreateFontString(nil, "OVERLAY")
    modelGuildText:SetFont(GetFont(), 9, "OUTLINE")
    modelGuildText:SetPoint("TOP", modelNameText, "BOTTOM", 0, -1)
    modelGuildText:SetJustifyH("CENTER")
    modelGuildText:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
    equipPage.modelGuildText = modelGuildText

    -- Model toolbar: unified strip with 3 segments (matches CharacterPanel)
    local tbH = 14
    local segW = 24
    local tbW = segW * 3 + 2
    local toolbar = CreateFrame("Frame", nil, equipPage)
    toolbar:SetWidth(tbW)
    toolbar:SetHeight(tbH)
    toolbar:SetPoint("BOTTOM", modelBg, "BOTTOM", 0, 5)
    toolbar:SetFrameLevel(equipPage:GetFrameLevel() + 6)
    SetPixelBackdrop(toolbar, { 0.04, 0.04, 0.06, 0.75 }, { 0.22, 0.22, 0.28, 0.5 })
    equipPage.modelToolbar = toolbar

    local function MakeToolBtn(parent, w, text, ox)
        local btn = CreateFrame("Button", nil, parent)
        btn:SetWidth(w)
        btn:SetHeight(tbH - 2)
        btn:SetPoint("LEFT", parent, "LEFT", ox, 0)
        btn:SetFrameLevel(parent:GetFrameLevel() + 1)
        btn:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            tile = false, tileSize = 0, edgeSize = 0,
        })
        btn:SetBackdropColor(0, 0, 0, 0)
        local fs = MakeFS(btn, 9, "CENTER", { 0.5, 0.5, 0.55 })
        fs:SetPoint("CENTER", btn, "CENTER", 0, 0)
        fs:SetText(text)
        btn.label = fs
        btn:SetScript("OnEnter", function()
            this:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4] or 0.5)
            this.label:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
        end)
        btn:SetScript("OnLeave", function()
            this:SetBackdropColor(0, 0, 0, 0)
            this.label:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
        end)
        return btn
    end

    local rotLeft = MakeToolBtn(toolbar, segW, "<", 1)
    rotLeft:SetScript("OnMouseDown", function()
        model.autoRotateDir = 1
        this:SetBackdropColor(T.slotHover[1], T.slotHover[2], T.slotHover[3], T.slotHover[4] or 0.6)
    end)
    rotLeft:SetScript("OnMouseUp", function()
        model.autoRotateDir = nil
        this:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4] or 0.5)
    end)
    rotLeft:SetScript("OnLeave", function()
        this:SetBackdropColor(0, 0, 0, 0)
        this.label:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
        model.autoRotateDir = nil
    end)
    equipPage.rotLeft = rotLeft

    local sep1 = toolbar:CreateTexture(nil, "OVERLAY")
    sep1:SetTexture("Interface\\Buttons\\WHITE8X8")
    sep1:SetVertexColor(T.sepColor[1], T.sepColor[2], T.sepColor[3], T.sepColor[4] or 0.4)
    sep1:SetWidth(1)
    sep1:SetHeight(tbH - 4)
    sep1:SetPoint("LEFT", toolbar, "LEFT", segW + 1, 0)

    local resetBtn = MakeToolBtn(toolbar, segW, "O", segW + 1)
    resetBtn:SetScript("OnClick", function() model:ResetView() end)
    resetBtn:SetScript("OnEnter", function()
        this:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4] or 0.5)
        this.label:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
        GameTooltip:SetOwner(this, "ANCHOR_BOTTOM")
        GameTooltip:AddLine("重置视角", 1, 1, 1)
        GameTooltip:Show()
    end)
    resetBtn:SetScript("OnLeave", function()
        this:SetBackdropColor(0, 0, 0, 0)
        this.label:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
        GameTooltip:Hide()
    end)
    equipPage.resetBtn = resetBtn

    local sep2 = toolbar:CreateTexture(nil, "OVERLAY")
    sep2:SetTexture("Interface\\Buttons\\WHITE8X8")
    sep2:SetVertexColor(T.sepColor[1], T.sepColor[2], T.sepColor[3], T.sepColor[4] or 0.4)
    sep2:SetWidth(1)
    sep2:SetHeight(tbH - 4)
    sep2:SetPoint("LEFT", toolbar, "LEFT", segW * 2 + 1, 0)

    local rotRight = MakeToolBtn(toolbar, segW, ">", segW * 2 + 1)
    rotRight:SetScript("OnMouseDown", function()
        model.autoRotateDir = -1
        this:SetBackdropColor(T.slotHover[1], T.slotHover[2], T.slotHover[3], T.slotHover[4] or 0.6)
    end)
    rotRight:SetScript("OnMouseUp", function()
        model.autoRotateDir = nil
        this:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4] or 0.5)
    end)
    rotRight:SetScript("OnLeave", function()
        this:SetBackdropColor(0, 0, 0, 0)
        this.label:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
        model.autoRotateDir = nil
    end)
    equipPage.rotRight = rotRight

    local slotY = -6
    equipPage.equipSlots = {}

    for idx, si in ipairs(EQUIP_SLOTS_LEFT) do
        local slot = self:CreateEquipSlot(child, si.id, si.name)
        slot:SetPoint("TOPLEFT", child, "TOPLEFT", 2, slotY - (idx - 1) * (SLOT_SIZE + SLOT_GAP))
        equipPage.equipSlots[si.id] = slot
    end

    for idx, si in ipairs(EQUIP_SLOTS_RIGHT) do
        local slot = self:CreateEquipSlot(child, si.id, si.name)
        slot:SetPoint("TOPRIGHT", child, "TOPRIGHT", -2, slotY - (idx - 1) * (SLOT_SIZE + SLOT_GAP))
        equipPage.equipSlots[si.id] = slot
    end

    local bottomY = slotY - table.getn(EQUIP_SLOTS_LEFT) * (SLOT_SIZE + SLOT_GAP) - 4
    local bottomCount = table.getn(EQUIP_SLOTS_BOTTOM)
    local totalBW = bottomCount * SLOT_SIZE + (bottomCount - 1) * (SLOT_GAP + 2)
    local bsx = math.max((cw - totalBW) / 2, 4)

    for idx, si in ipairs(EQUIP_SLOTS_BOTTOM) do
        local slot = self:CreateEquipSlot(child, si.id, si.name)
        slot:SetPoint("TOPLEFT", child, "TOPLEFT", bsx + (idx - 1) * (SLOT_SIZE + SLOT_GAP + 1), bottomY)
        equipPage.equipSlots[si.id] = slot
    end

    local infoY = bottomY - SLOT_SIZE - 2
    equipPage.totalContentH = math.abs(infoY) + 8
    scrollArea:SetContentHeight(equipPage.totalContentH)
end

local ALL_EQUIP_SLOTS = nil
local function GetAllEquipSlots()
    if not ALL_EQUIP_SLOTS then
        ALL_EQUIP_SLOTS = {}
        for _, t in ipairs({ EQUIP_SLOTS_LEFT, EQUIP_SLOTS_RIGHT, EQUIP_SLOTS_BOTTOM }) do
            for _, s in ipairs(t) do table.insert(ALL_EQUIP_SLOTS, s) end
        end
    end
    return ALL_EQUIP_SLOTS
end

function IP:UpdateEquipment()
    if not equipPage or not equipPage.built then return end
    local unit = GetInspectUnit()

    if equipPage.model then
        equipPage.model:Show()
        if equipPage.modelFrame then equipPage.modelFrame:Show() end
        if equipPage.modelBgFrame then equipPage.modelBgFrame:Show() end
        if equipPage.modelToolbar then equipPage.modelToolbar:Show() end
        equipPage.model:SetUnit(unit)
        equipPage.model:SetFacing(equipPage.model.curFacing or 0.4)
        equipPage.model:SetModelScale(equipPage.model.curScale or 1.0)
        equipPage.model:SetPosition(equipPage.model.posY or 0, 0, equipPage.model.posX or 0)
    end

    local allSlots = GetAllEquipSlots()
    local showIlvl = (not SFramesDB or SFramesDB.showItemLevel ~= false) and LibItem_Level
    local totalIlvl, slotCount = 0, 0
    local scoreSlots = { [1]=true,[2]=true,[3]=true,[5]=true,[6]=true,[7]=true,[8]=true,
        [9]=true,[10]=true,[11]=true,[12]=true,[13]=true,[14]=true,[15]=true,[16]=true,[17]=true,[18]=true }

    for _, si in ipairs(allSlots) do
        local slot = equipPage.equipSlots[si.id]
        if slot then
            local tex = GetInventoryItemTexture(unit, si.id)
            local link = GetInventoryItemLink(unit, si.id)
            if tex then
                slot.icon:SetTexture(tex)
                slot.icon:SetVertexColor(1, 1, 1)
                if slot.ilvlText then
                    if showIlvl and link then
                        local _, _, itemId = string.find(link, "item:(%d+)")
                        local ilvl = itemId and LibItem_Level[tonumber(itemId)]
                        slot.ilvlText:SetText(ilvl and tostring(ilvl) or "")
                        if ilvl and ilvl > 0 and scoreSlots[si.id] then
                            totalIlvl = totalIlvl + ilvl
                            slotCount = slotCount + 1
                        end
                    else
                        slot.ilvlText:SetText("")
                    end
                end
                local quality = GetItemQualityFromLink(link)
                if quality and quality >= 2 and QUALITY_COLORS[quality] then
                    local qc = QUALITY_COLORS[quality]
                    slot.qualGlow:SetVertexColor(qc[1], qc[2], qc[3])
                    slot.qualGlow:Show()
                else
                    slot.qualGlow:Hide()
                end
            else
                if slot.emptyTexture then
                    slot.icon:SetTexture(slot.emptyTexture)
                    slot.icon:SetVertexColor(T.emptySlot[1], T.emptySlot[2], T.emptySlot[3], T.emptySlot[4])
                else slot.icon:SetTexture(nil) end
                slot:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
                slot.currentBorderColor = nil
                slot.qualGlow:Hide()
                if slot.ilvlText then slot.ilvlText:SetText("") end
            end
        end
    end

    equipPage.avgIlvl = nil
    if slotCount > 0 then
        equipPage.avgIlvl = totalIlvl / slotCount
    end
    IP:UpdateTitle()
end

--------------------------------------------------------------------------------
-- Inspect Summary Side Panel (gear stats + equipment list)
--------------------------------------------------------------------------------
local inspSummary
local SUMMARY_W = 220
local SUMMARY_H = 380
local SUMMARY_HEADER_H = 24
local SUMMARY_ROW_H = 14
local SUMMARY_SECTION_GAP = 4

local ENCHANTABLE_INSPECT_SLOTS = {
    { id = 1,  name = "HeadSlot",          label = "头部" },
    { id = 3,  name = "ShoulderSlot",      label = "肩部" },
    { id = 15, name = "BackSlot",          label = "背部" },
    { id = 5,  name = "ChestSlot",         label = "胸部" },
    { id = 9,  name = "WristSlot",         label = "手腕" },
    { id = 10, name = "HandsSlot",         label = "手套" },
    { id = 7,  name = "LegsSlot",          label = "腿部" },
    { id = 8,  name = "FeetSlot",          label = "脚部" },
    { id = 11, name = "Finger0Slot",       label = "戒指1" },
    { id = 12, name = "Finger1Slot",       label = "戒指2" },
    { id = 16, name = "MainHandSlot",      label = "主手" },
    { id = 17, name = "SecondaryHandSlot", label = "副手" },
    { id = 18, name = "RangedSlot",        label = "远程" },
}

local inspScanTip

local function EnsureInspTip()
    if not inspScanTip then
        inspScanTip = CreateFrame("GameTooltip", "SFramesIPScanTip", nil, "GameTooltipTemplate")
    end
    inspScanTip:SetOwner(UIParent, "ANCHOR_NONE")
    return inspScanTip
end

local STAT_PATS = {
    { p = "%+(%d+)%s*力量", k = "str" },
    { p = "%+(%d+)%s*敏捷", k = "agi" },
    { p = "%+(%d+)%s*耐力", k = "sta" },
    { p = "%+(%d+)%s*智力", k = "int" },
    { p = "%+(%d+)%s*精神", k = "spi" },
    { p = "%+(%d+)%s*Strength", k = "str" },
    { p = "%+(%d+)%s*Agility", k = "agi" },
    { p = "%+(%d+)%s*Stamina", k = "sta" },
    { p = "%+(%d+)%s*Intellect", k = "int" },
    { p = "%+(%d+)%s*Spirit", k = "spi" },
    { p = "%+(%d+)%s*火焰抗性", k = "fireres" },
    { p = "%+(%d+)%s*自然抗性", k = "natureres" },
    { p = "%+(%d+)%s*冰霜抗性", k = "frostres" },
    { p = "%+(%d+)%s*暗影抗性", k = "shadowres" },
    { p = "%+(%d+)%s*奥术抗性", k = "arcaneres" },
    { p = "%+(%d+)%s*所有抗性", k = "allres" },
    { p = "%+(%d+)%s*Fire Resistance", k = "fireres" },
    { p = "%+(%d+)%s*Nature Resistance", k = "natureres" },
    { p = "%+(%d+)%s*Frost Resistance", k = "frostres" },
    { p = "%+(%d+)%s*Shadow Resistance", k = "shadowres" },
    { p = "%+(%d+)%s*Arcane Resistance", k = "arcaneres" },
    { p = "%+(%d+)%s*All Resistances", k = "allres" },
    { p = "%+(%d+)%s*攻击强度", k = "ap" },
    { p = "%+(%d+)%s*Attack Power", k = "ap" },
}

local EQUIP_PATS = {
    { p = "伤害和治疗效果.-最多(%d+)点", k = "spelldmg" },
    { p = "法术造成的治疗效果.-最多(%d+)点", k = "healing" },
    { p = "击中几率提高(%d+)%%", k = "hit" },
    { p = "暴击几率提高(%d+)%%", k = "crit" },
    { p = "法术暴击几率提高(%d+)%%", k = "spellcrit" },
    { p = "法术命中几率提高(%d+)%%", k = "spellhit" },
    { p = "每5秒恢复(%d+)点法力", k = "mp5" },
    { p = "防御技能提高(%d+)", k = "defense" },
    { p = "躲闪几率提高(%d+)%%", k = "dodge" },
    { p = "招架几率提高(%d+)%%", k = "parry" },
    { p = "格挡几率提高(%d+)%%", k = "block" },
    { p = "damage and healing done by magical spells.-by up to (%d+)", k = "spelldmg" },
    { p = "healing done by spells.-by up to (%d+)", k = "healing" },
    { p = "chance to hit by (%d+)%%", k = "hit" },
    { p = "critical strike by (%d+)%%", k = "crit" },
    { p = "spell critical chance by (%d+)%%", k = "spellcrit" },
    { p = "Restores (%d+) mana per 5 sec", k = "mp5" },
    { p = "defense skill by (%d+)", k = "defense" },
    { p = "chance to dodge.-by (%d+)%%", k = "dodge" },
    { p = "chance to parry.-by (%d+)%%", k = "parry" },
    { p = "chance to block.-by (%d+)%%", k = "block" },
}

local PROC_INSP_ENCHANTS = {
    "十字军", "Crusader", "吸取生命", "Lifestealing",
    "灼热武器", "Fiery Weapon", "火焰武器",
    "寒冰", "Icy Chill", "邪恶武器", "Unholy Weapon",
    "恶魔杀手", "Demonslaying", "无法被缴械", "Cannot be Disarmed",
}

local function IsInspEnchantLine(txt)
    if string.find(txt, "%+%d") then return true end
    if string.find(txt, "%d+%%") then return true end
    for i = 1, table.getn(PROC_INSP_ENCHANTS) do
        if string.find(txt, PROC_INSP_ENCHANTS[i]) then return true end
    end
    return false
end

local function ScanInspectGearStats()
    local unit = GetInspectUnit()
    local stats = {}
    local allSlots = GetAllEquipSlots()
    local tip = EnsureInspTip()

    for _, si in ipairs(allSlots) do
        local link = GetInventoryItemLink(unit, si.id)
        if link then
            tip:ClearLines()
            tip:SetInventoryItem(unit, si.id)
            local n = tip:NumLines()
            if n and n > 0 then
                for li = 1, n do
                    local obj = _G["SFramesIPScanTipTextLeft" .. li]
                    if obj then
                        local txt = obj:GetText()
                        if txt and txt ~= "" then
                            for _, sp in ipairs(STAT_PATS) do
                                local _, _, val = string.find(txt, sp.p)
                                if val then
                                    stats[sp.k] = (stats[sp.k] or 0) + tonumber(val)
                                end
                            end
                            local r, g, b = obj:GetTextColor()
                            if g > 0.8 and r < 0.5 and b < 0.5 then
                                for _, ep in ipairs(EQUIP_PATS) do
                                    local _, _, val = string.find(txt, ep.p)
                                    if val then
                                        stats[ep.k] = (stats[ep.k] or 0) + tonumber(val)
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end

    if stats.allres and stats.allres > 0 then
        local ar = stats.allres
        stats.fireres = (stats.fireres or 0) + ar
        stats.natureres = (stats.natureres or 0) + ar
        stats.frostres = (stats.frostres or 0) + ar
        stats.shadowres = (stats.shadowres or 0) + ar
        stats.arcaneres = (stats.arcaneres or 0) + ar
        stats.allres = nil
    end

    return stats
end

local function HasAnyStats(stats)
    if not stats then return false end
    for k, v in pairs(stats) do
        if v and v > 0 then return true end
    end
    return false
end

local function GetInspectEnchant(slotId)
    local unit = GetInspectUnit()
    local tip = EnsureInspTip()
    tip:ClearLines()
    tip:SetInventoryItem(unit, slotId)
    local n = tip:NumLines()
    if not n or n < 2 then return false, nil end

    for i = 2, n do
        local obj = _G["SFramesIPScanTipTextLeft" .. i]
        if obj then
            local txt = obj:GetText()
            if txt and txt ~= "" then
                local r, g, b = obj:GetTextColor()
                if g > 0.8 and r < 0.5 and b < 0.5 then
                    local skip = false
                    if string.find(txt, "装备：") or string.find(txt, "装备:") or string.find(txt, "Equip:") then
                        skip = true
                    elseif string.find(txt, "使用：") or string.find(txt, "使用:") or string.find(txt, "Use:") then
                        skip = true
                    elseif string.find(txt, "击中时可能") or string.find(txt, "Chance on hit") then
                        skip = true
                    elseif string.find(txt, "^%(") then
                        skip = true
                    elseif string.find(txt, "套装：") or string.find(txt, "套装:") or string.find(txt, "Set:") then
                        skip = true
                    end
                    if not skip and IsInspEnchantLine(txt) then
                        return true, txt
                    end
                end
            end
        end
    end
    return false, nil
end

-- Row helpers for summary panel
local function HideSummaryRows(parent)
    if parent._r then
        for i = 1, table.getn(parent._r) do
            local r = parent._r[i]
            if r and r.Hide then r:Hide() end
        end
    end
    parent._r = {}
end

local function AddSummaryHeader(p, txt, y, clr)
    local f1 = MakeFS(p, 10, "LEFT", clr or T.sectionTitle)
    f1:SetPoint("TOPLEFT", p, "TOPLEFT", 4, y)
    f1:SetText(txt)
    table.insert(p._r, f1)

    local s = p:CreateTexture(nil, "ARTWORK")
    s:SetTexture("Interface\\Buttons\\WHITE8X8")
    s:SetVertexColor(T.sepColor[1], T.sepColor[2], T.sepColor[3], T.sepColor[4])
    s:SetHeight(1)
    s:SetPoint("TOPLEFT", p, "TOPLEFT", 4, y - 13)
    s:SetPoint("TOPRIGHT", p, "TOPRIGHT", -4, y - 13)
    table.insert(p._r, s)
    return y - 16
end

local function AddSummaryRow(p, lbl, val, y, lc, vc)
    local f1 = MakeFS(p, 9, "LEFT", lc or T.labelText)
    f1:SetPoint("TOPLEFT", p, "TOPLEFT", 8, y)
    f1:SetText(lbl)
    table.insert(p._r, f1)

    local f2 = MakeFS(p, 9, "RIGHT", vc or T.valueText)
    f2:SetPoint("TOPRIGHT", p, "TOPRIGHT", -12, y)
    f2:SetWidth(80)
    f2:SetJustifyH("RIGHT")
    f2:SetText(val)
    table.insert(p._r, f2)
    return y - SUMMARY_ROW_H
end

local INSP_STAT_COLORS = {
    str = { 0.78, 0.61, 0.43 }, agi = { 0.52, 1, 0.52 },
    sta = { 0.75, 0.55, 0.25 }, int = { 0.41, 0.80, 0.94 },
    spi = { 1, 1, 1 },
}
local INSP_RESIST_COLORS = {
    arcaneres = { 0.95, 0.90, 0.40 }, fireres   = { 1, 0.50, 0.15 },
    natureres = { 0.35, 0.90, 0.25 }, frostres  = { 0.45, 0.70, 1.00 },
    shadowres = { 0.60, 0.35, 0.90 },
}
local INSP_PHYS_COLOR  = { 0.90, 0.75, 0.55 }
local INSP_SPELL_COLOR = { 0.60, 0.80, 1.00 }
local INSP_REGEN_COLOR = { 0.40, 0.90, 0.70 }
local INSP_DEF_COLOR   = { 0.35, 0.90, 0.25 }
local INSP_ENCHANTED   = { 0.30, 1, 0.30 }
local INSP_NO_ENCHANT  = { 1, 0.35, 0.35 }

local function BuildInspectSummary()
    if inspSummary then return inspSummary end

    local f = CreateFrame("Frame", "SFramesInspectSummary", UIParent)
    f:SetWidth(SUMMARY_W)
    f:SetHeight(SUMMARY_H)
    f:SetFrameStrata("HIGH")
    f:EnableMouse(true)
    f:SetMovable(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", function() this:StartMoving() end)
    f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
    f:SetClampedToScreen(true)
    SetRoundBackdrop(f, T.bg, T.border)
    CreateShadow(f, 4)
    f:Hide()

    local hdr = MakeFS(f, 11, "LEFT", T.gold)
    hdr:SetPoint("TOPLEFT", f, "TOPLEFT", 8, -5)
    hdr:SetText("装备属性")

    local tabW = 52
    f.tabs = {}
    f.curTab = 1
    local tNames = { "属性", "装备" }
    for i = 1, 2 do
        local btn = CreateFrame("Button", NextName("IST"), f)
        btn:SetWidth(tabW)
        btn:SetHeight(16)
        btn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -(8 + (2 - i) * (tabW + 2) + 18), -4)
        btn:SetFrameLevel(f:GetFrameLevel() + 2)
        SetPixelBackdrop(btn, T.btnBg, T.btnBorder)
        local lbl = MakeFS(btn, 9, "CENTER", T.dimText)
        lbl:SetPoint("CENTER", btn, "CENTER", 0, 0)
        lbl:SetText(tNames[i])
        btn.lbl = lbl
        btn.idx = i
        btn:SetScript("OnClick", function() IP:SetSummaryTab(this.idx) end)
        btn:SetScript("OnEnter", function()
            if this.idx ~= (inspSummary.curTab or 1) then
                this:SetBackdropColor(T.btnHover[1], T.btnHover[2], T.btnHover[3], T.btnHover[4])
            end
        end)
        btn:SetScript("OnLeave", function()
            if this.idx ~= (inspSummary.curTab or 1) then
                SetPixelBackdrop(this, T.btnBg, T.btnBorder)
            end
        end)
        f.tabs[i] = btn
    end

    local closeBtn = CreateFrame("Button", nil, f)
    closeBtn:SetWidth(14)
    closeBtn:SetHeight(14)
    closeBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -4, -5)
    closeBtn:SetFrameLevel(f:GetFrameLevel() + 3)
    SetRoundBackdrop(closeBtn, T.buttonDownBg, T.btnBorder)
    local closeTxt = MakeFS(closeBtn, 8, "CENTER", T.title)
    closeTxt:SetPoint("CENTER", closeBtn, "CENTER", 0, 0)
    closeTxt:SetText("x")
    closeBtn:SetScript("OnClick", function() f:Hide() end)
    closeBtn: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)
    closeBtn:SetScript("OnLeave", function()
        this:SetBackdropColor(T.buttonDownBg[1], T.buttonDownBg[2], T.buttonDownBg[3], T.buttonDownBg[4])
        this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
    end)

    MakeSep(f, 4, -SUMMARY_HEADER_H, -4, -SUMMARY_HEADER_H)

    local cH = SUMMARY_H - SUMMARY_HEADER_H - 8
    local cW = SUMMARY_W - 8

    f.statsScroll = CreateScrollFrame(f, cW, cH)
    f.statsScroll:SetPoint("TOPLEFT", f, "TOPLEFT", 4, -(SUMMARY_HEADER_H + 2))

    f.equipScroll = CreateScrollFrame(f, cW, cH)
    f.equipScroll:SetPoint("TOPLEFT", f, "TOPLEFT", 4, -(SUMMARY_HEADER_H + 2))
    f.equipScroll:Hide()

    inspSummary = f
    return f
end

function IP:SetSummaryTab(idx)
    if not inspSummary then return end
    inspSummary.curTab = idx
    for i = 1, 2 do
        local btn = inspSummary.tabs[i]
        if i == idx then
            SetPixelBackdrop(btn, T.tabActiveBg, T.tabActiveBorder)
            btn.lbl:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
        else
            SetPixelBackdrop(btn, T.btnBg, T.btnBorder)
            btn.lbl:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
        end
    end
    if idx == 1 then
        inspSummary.statsScroll:Show()
        inspSummary.equipScroll:Hide()
        local ok, err = pcall(function() IP:BuildInspectStats() end)
        if not ok then
            DEFAULT_CHAT_FRAME:AddMessage("|cffff4444IP BuildStats err: " .. tostring(err) .. "|r")
        end
    else
        inspSummary.statsScroll:Hide()
        inspSummary.equipScroll:Show()
        local ok, err = pcall(function() IP:BuildInspectEquipList() end)
        if not ok then
            DEFAULT_CHAT_FRAME:AddMessage("|cffff4444IP BuildEquip err: " .. tostring(err) .. "|r")
        end
    end
end

function IP:BuildInspectStats()
    if not inspSummary then return end
    local child = inspSummary.statsScroll.child
    HideSummaryRows(child)
    local y = -4

    local stats = ScanInspectGearStats()

    local pNames = {
        { k = "str", n = "力量" }, { k = "agi", n = "敏捷" },
        { k = "sta", n = "耐力" }, { k = "int", n = "智力" },
        { k = "spi", n = "精神" },
    }
    local hasP = false
    for _, p in ipairs(pNames) do
        if stats[p.k] and stats[p.k] > 0 then hasP = true; break end
    end
    if hasP then
        y = AddSummaryHeader(child, "装备主属性:", y)
        for _, p in ipairs(pNames) do
            local v = stats[p.k] or 0
            if v > 0 then
                local c = INSP_STAT_COLORS[p.k] or T.valueText
                y = AddSummaryRow(child, p.n, "+" .. tostring(v), y, c, c)
            end
        end
        y = y - SUMMARY_SECTION_GAP
    end

    local hasPhys = (stats.ap and stats.ap > 0) or (stats.hit and stats.hit > 0) or (stats.crit and stats.crit > 0)
    if hasPhys then
        y = AddSummaryHeader(child, "物理:", y, INSP_PHYS_COLOR)
        if stats.ap and stats.ap > 0 then
            y = AddSummaryRow(child, "攻击强度", "+" .. tostring(stats.ap), y, nil, INSP_PHYS_COLOR)
        end
        if stats.hit and stats.hit > 0 then
            y = AddSummaryRow(child, "命中", "+" .. tostring(stats.hit) .. "%", y)
        end
        if stats.crit and stats.crit > 0 then
            y = AddSummaryRow(child, "暴击", "+" .. tostring(stats.crit) .. "%", y)
        end
        y = y - SUMMARY_SECTION_GAP
    end

    local hasSpell = (stats.spelldmg and stats.spelldmg > 0) or (stats.healing and stats.healing > 0)
        or (stats.spellcrit and stats.spellcrit > 0) or (stats.spellhit and stats.spellhit > 0)
    if hasSpell then
        y = AddSummaryHeader(child, "法术:", y, INSP_SPELL_COLOR)
        if stats.spelldmg and stats.spelldmg > 0 then
            y = AddSummaryRow(child, "法术伤害", "+" .. tostring(stats.spelldmg), y, nil, INSP_SPELL_COLOR)
        end
        if stats.healing and stats.healing > 0 then
            y = AddSummaryRow(child, "法术治疗", "+" .. tostring(stats.healing), y)
        end
        if stats.spellcrit and stats.spellcrit > 0 then
            y = AddSummaryRow(child, "法术暴击", "+" .. tostring(stats.spellcrit) .. "%", y)
        end
        if stats.spellhit and stats.spellhit > 0 then
            y = AddSummaryRow(child, "法术命中", "+" .. tostring(stats.spellhit) .. "%", y)
        end
        y = y - SUMMARY_SECTION_GAP
    end

    local hasDef = (stats.defense and stats.defense > 0) or (stats.dodge and stats.dodge > 0)
        or (stats.parry and stats.parry > 0) or (stats.block and stats.block > 0)
    if hasDef then
        y = AddSummaryHeader(child, "防御:", y, INSP_DEF_COLOR)
        if stats.defense and stats.defense > 0 then
            y = AddSummaryRow(child, "防御技能", "+" .. tostring(stats.defense), y, nil, INSP_DEF_COLOR)
        end
        if stats.dodge and stats.dodge > 0 then
            y = AddSummaryRow(child, "躲闪", "+" .. tostring(stats.dodge) .. "%", y)
        end
        if stats.parry and stats.parry > 0 then
            y = AddSummaryRow(child, "招架", "+" .. tostring(stats.parry) .. "%", y)
        end
        if stats.block and stats.block > 0 then
            y = AddSummaryRow(child, "格挡", "+" .. tostring(stats.block) .. "%", y)
        end
        y = y - SUMMARY_SECTION_GAP
    end

    if stats.mp5 and stats.mp5 > 0 then
        y = AddSummaryHeader(child, "回复:", y, INSP_REGEN_COLOR)
        y = AddSummaryRow(child, "装备回蓝", tostring(stats.mp5) .. " MP/5s", y, nil, INSP_REGEN_COLOR)
        y = y - SUMMARY_SECTION_GAP
    end

    local resInfo = {
        { k = "arcaneres", n = "奥术抗性" }, { k = "fireres", n = "火焰抗性" },
        { k = "natureres", n = "自然抗性" }, { k = "frostres", n = "冰霜抗性" },
        { k = "shadowres", n = "暗影抗性" },
    }
    local hasRes = false
    for _, ri in ipairs(resInfo) do
        if stats[ri.k] and stats[ri.k] > 0 then hasRes = true; break end
    end
    if hasRes then
        y = AddSummaryHeader(child, "抗性:", y, INSP_RESIST_COLORS.arcaneres)
        for _, ri in ipairs(resInfo) do
            local v = stats[ri.k] or 0
            if v > 0 then
                local c = INSP_RESIST_COLORS[ri.k] or T.valueText
                y = AddSummaryRow(child, ri.n, "+" .. tostring(v), y, c, c)
            end
        end
        y = y - SUMMARY_SECTION_GAP
    end

    if not HasAnyStats(stats) then
        y = AddSummaryHeader(child, "提示:", y, T.dimText)
        local noData = MakeFS(child, 9, "LEFT", T.dimText)
        noData:SetPoint("TOPLEFT", child, "TOPLEFT", 8, y)
        noData:SetText("未获取到装备属性数据")
        table.insert(child._r, noData)
        y = y - SUMMARY_ROW_H
    end

    inspSummary.statsScroll:SetContentHeight(math.abs(y) + 12)
end

function IP:BuildInspectEquipList()
    if not inspSummary then return end
    local child = inspSummary.equipScroll.child
    HideSummaryRows(child)
    local y = -4

    y = AddSummaryHeader(child, "装备列表 & 附魔检查:", y, T.gold)

    local unit = GetInspectUnit()
    local totalSlots = 0
    local enchCount = 0

    for si = 1, table.getn(ENCHANTABLE_INSPECT_SLOTS) do
        local slot = ENCHANTABLE_INSPECT_SLOTS[si]
        local link = GetInventoryItemLink(unit, slot.id)

        if link then
            totalSlots = totalSlots + 1
            local _, _, rawName = string.find(link, "%[(.-)%]")
            local itemName = rawName or slot.label

            local quality = nil
            local _, _, qH = string.find(link, "|c(%x+)|H")
            local qMap = {
                ["ff9d9d9d"] = 0, ["ffffffff"] = 1, ["ff1eff00"] = 2,
                ["ff0070dd"] = 3, ["ffa335ee"] = 4, ["ffff8000"] = 5,
            }
            if qH then quality = qMap[qH] end
            local nc = (quality and QUALITY_COLORS[quality]) or T.valueText

            local sf = MakeFS(child, 8, "LEFT", T.dimText)
            sf:SetPoint("TOPLEFT", child, "TOPLEFT", 4, y)
            sf:SetText(slot.label)
            sf:SetWidth(32)
            table.insert(child._r, sf)

            local nf = MakeFS(child, 9, "LEFT", nc)
            nf:SetPoint("TOPLEFT", child, "TOPLEFT", 38, y)
            nf:SetWidth(120)
            if string.len(itemName) > 16 then
                itemName = string.sub(itemName, 1, 14) .. ".."
            end
            nf:SetText(itemName)
            table.insert(child._r, nf)

            local hasE, eTxt = false, nil
            local eOk, eR1, eR2 = pcall(GetInspectEnchant, slot.id)
            if eOk then hasE = eR1; eTxt = eR2 end

            local ico = MakeFS(child, 9, "RIGHT")
            ico:SetPoint("TOPRIGHT", child, "TOPRIGHT", -8, y)
            if hasE then
                ico:SetTextColor(INSP_ENCHANTED[1], INSP_ENCHANTED[2], INSP_ENCHANTED[3])
                ico:SetText("*")
                enchCount = enchCount + 1
            else
                ico:SetTextColor(INSP_NO_ENCHANT[1], INSP_NO_ENCHANT[2], INSP_NO_ENCHANT[3])
                ico:SetText("-")
            end
            table.insert(child._r, ico)

            y = y - SUMMARY_ROW_H

            if hasE and eTxt then
                local ef = MakeFS(child, 8, "LEFT", INSP_ENCHANTED)
                ef:SetPoint("TOPLEFT", child, "TOPLEFT", 38, y)
                ef:SetWidth(145)
                ef:SetText("  " .. eTxt)
                table.insert(child._r, ef)
                y = y - 12
            elseif not hasE then
                local ef = MakeFS(child, 8, "LEFT", INSP_NO_ENCHANT)
                ef:SetPoint("TOPLEFT", child, "TOPLEFT", 38, y)
                ef:SetText("  未附魔")
                table.insert(child._r, ef)
                y = y - 12
            end
            y = y - 2
        else
            local sf = MakeFS(child, 8, "LEFT", T.dimText)
            sf:SetPoint("TOPLEFT", child, "TOPLEFT", 4, y)
            sf:SetText(slot.label)
            table.insert(child._r, sf)

            local ef = MakeFS(child, 9, "LEFT", T.dimText)
            ef:SetPoint("TOPLEFT", child, "TOPLEFT", 38, y)
            ef:SetText("-- 未装备 --")
            table.insert(child._r, ef)
            y = y - SUMMARY_ROW_H - 2
        end
    end

    y = y - SUMMARY_SECTION_GAP
    y = AddSummaryHeader(child, "附魔统计:", y, T.gold)
    local sc2 = (enchCount == totalSlots and totalSlots > 0) and INSP_ENCHANTED or INSP_NO_ENCHANT
    y = AddSummaryRow(child, "已附魔/总装备", enchCount .. "/" .. totalSlots, y, nil, sc2)
    if enchCount < totalSlots then
        y = AddSummaryRow(child, "缺少附魔", tostring(totalSlots - enchCount) .. " 件", y, INSP_NO_ENCHANT, INSP_NO_ENCHANT)
    end

    inspSummary.equipScroll:SetContentHeight(math.abs(y) + 12)
end

function IP:ShowSummary()
    BuildInspectSummary()
    if ipanel and ipanel:IsShown() then
        inspSummary:ClearAllPoints()
        inspSummary:SetPoint("TOPLEFT", ipanel, "TOPRIGHT", 2, 0)
    else
        inspSummary:ClearAllPoints()
        inspSummary:SetPoint("CENTER", UIParent, "CENTER", 200, 0)
    end
    inspSummary:Show()

    IP:SetSummaryTab(2)
end

function IP:HideSummary()
    if inspSummary then inspSummary:Hide() end
end

function IP:RefreshSummary()
    if not inspSummary or not inspSummary:IsShown() then return end
    IP:SetSummaryTab(inspSummary.curTab or 1)
end

--------------------------------------------------------------------------------
-- Open native InspectFrame for a specific tab (honor / talent)
-- Turtle WoW tabs: 1=角色, 2=荣誉, 3=天赋, ...
--------------------------------------------------------------------------------
local NATIVE_TAB_INDEX = { honor = 2, talent = 3 }

function IP:OpenNativeTab(which)
    local unit = inspectUnit or "target"
    if not UnitExists(unit) then return end

    local tabIdx = NATIVE_TAB_INDEX[which]

    self.switchingToNative = true
    self.allowNativeFrame = true

    if ipanel then ipanel:Hide() end
    self.switchingToNative = false

    if not IsAddOnLoaded("Blizzard_InspectUI") then
        pcall(function() LoadAddOn("Blizzard_InspectUI") end)
    end

    local inspF = _G["InspectFrame"]
    if inspF and inspF._origShow then
        inspF.Show = inspF._origShow
        inspF._origShow = nil
    end

    if IP._origInspectUnit then
        IP._origInspectUnit(unit)
    end

    inspF = _G["InspectFrame"]
    if inspF then
        inspF:SetAlpha(1)
        inspF:Show()
    end

    if tabIdx then
        local tab = _G["InspectFrameTab" .. tabIdx]
        if tab and tab.Click then
            tab:Click()
        elseif tab and tab:GetScript("OnClick") then
            tab:GetScript("OnClick")()
        end
    end

    if not self._resetFrame then
        self._resetFrame = CreateFrame("Frame", nil, UIParent)
    end
    self._resetFrame.elapsed = 0
    self._resetFrame:SetScript("OnUpdate", function()
        this.elapsed = (this.elapsed or 0) + (arg1 or 0)
        if this.elapsed > 1.0 then
            IP.allowNativeFrame = false
            this:SetScript("OnUpdate", nil)
        end
    end)
end

--------------------------------------------------------------------------------
-- Open / Close / Toggle
--------------------------------------------------------------------------------
function IP:Open(unit)
    inspectUnit = unit or "target"

    if not UnitExists(inspectUnit) then
        if SFrames and SFrames.Print then
            SFrames:Print("没有有效的观察目标。")
        end
        return
    end
    if not UnitIsPlayer(inspectUnit) then
        if SFrames and SFrames.Print then
            SFrames:Print("只能观察其他玩家。")
        end
        return
    end

    CreateMainFrame()
    self:BuildEquipmentPage()

    NotifyInspect(inspectUnit)

    ipanel:Show()
    self:UpdateTitle()
    self:UpdateEquipment()
    self:ShowSummary()
end

function IP:Close()
    self:HideSummary()
    if ipanel then ipanel:Hide() end
    if ClearInspectPlayer then ClearInspectPlayer() end
    inspectUnit = nil
end

function IP:Toggle(unit)
    if ipanel and ipanel:IsShown() then
        self:Close()
    else
        self:Open(unit)
    end
end

--------------------------------------------------------------------------------
-- Events & Hooking
--------------------------------------------------------------------------------
local eventFrame = CreateFrame("Frame", "SFramesIPEvents", UIParent)
eventFrame:RegisterEvent("UNIT_INVENTORY_CHANGED")
eventFrame:RegisterEvent("ADDON_LOADED")

eventFrame:SetScript("OnEvent", function()
    if event == "ADDON_LOADED" then
        -- Re-hook InspectUnit whenever a new addon loads (in case it replaces our hook)
        if type(InspectUnit) == "function" and InspectUnit ~= IP._ourHook then
            IP._origInspectUnit = InspectUnit
            local hook = function(unit)
                if (SFramesDB and SFramesDB.enableInspect == false) or IP.allowNativeFrame then
                    if IP._origInspectUnit then
                        IP._origInspectUnit(unit)
                    end
                    return
                end
                IP:Open(unit or "target")
            end
            IP._ourHook = hook
            InspectUnit = hook
        end
        return
    end

    if event == "UNIT_INVENTORY_CHANGED" then
        if not ipanel or not ipanel:IsShown() then return end
        local unit = GetInspectUnit()
        if arg1 and UnitIsUnit(arg1, unit) then
            IP:UpdateEquipment()
            IP:RefreshSummary()
        end
    end
end)

-- Hook UnitPopup_OnClick to catch right-click menu "Inspect"
if UnitPopup_OnClick then
    local orig_UnitPopup_OnClick = UnitPopup_OnClick
    UnitPopup_OnClick = function()
        local button = this and this.value
        if button == "INSPECT" and not (SFramesDB and SFramesDB.enableInspect == false) then
            local dropdownFrame = _G[UIDROPDOWNMENU_INIT_MENU]
            local unit = (dropdownFrame and dropdownFrame.unit) or "target"
            IP:Open(unit)
            return
        end
        return orig_UnitPopup_OnClick()
    end
end

-- Hook InspectUnit
local function HookInspectUnit()
    if type(InspectUnit) ~= "function" then return end
    if InspectUnit == IP._ourHook then return end
    IP._origInspectUnit = InspectUnit
    local hook = function(unit)
        if (SFramesDB and SFramesDB.enableInspect == false) or IP.allowNativeFrame then
            if IP._origInspectUnit then
                IP._origInspectUnit(unit)
            end
            return
        end
        IP:Open(unit or "target")
    end
    IP._ourHook = hook
    InspectUnit = hook
end

-- Hook ShowUIPanel to block native InspectFrame when we don't want it
if ShowUIPanel then
    local _origShowUIPanel = ShowUIPanel
    ShowUIPanel = function(frame)
        if frame and frame.GetName and not IP.allowNativeFrame and not (SFramesDB and SFramesDB.enableInspect == false) then
            local name = frame:GetName() or ""
            if string.find(name, "Inspect") then
                return
            end
        end
        return _origShowUIPanel(frame)
    end
end

HookInspectUnit()
