
-- ============================================================
-- FILE: InspectPanel.lua
-- PURPOSE: Custom inspect panel shown when examining another
--   player. Displays the inspected character's gear (item
--   icons, item level, quality colors), spec, class, level,
--   average item level, and character model. Replaces
--   Blizzard's InspectFrame.
-- HOW IT WORKS:
--   1. Panel table + Panel._refs: sub-frame references.
--   2. Panel.EnsurePanel() — lazy-creates the main Frame
--      (MidnightUI_InspectPanel) with header, character model
--      viewer (SetUnit to inspected unit), gear slot buttons
--      (GetInventoryItemLink/Texture for inspected unit),
--      spec/class/ilvl text rows.
--   3. Panel.Show(unit) — calls NotifyInspect(unit), then
--      waits for INSPECT_READY event to populate data.
--   4. Panel.Refresh() — called on INSPECT_READY; reads all
--      gear slots for the inspected unit and updates display.
--   5. eventFrame OnEvent: INSPECT_READY → Panel.Refresh();
--      PLAYER_ENTERING_WORLD → hide panel.
-- DEPENDS ON:
--   NotifyInspect, GetInventoryItemLink (for inspected unit)
--   GetSpecialization (target), GetAverageItemLevel (target)
-- EXPORTS:
--   _G.MidnightUI_InspectPanel_Show
--   _G.MidnightUI_InspectPanel_Hide
--   _G.MidnightUI_InspectPanel_Toggle
--   _G.MidnightUI_InspectPanel_IsOpen
-- OWNED SAVEDVARIABLES: none
-- EVENTS REGISTERED:
--   INSPECT_READY, PLAYER_ENTERING_WORLD
--   UNIT_INVENTORY_CHANGED (for inspected unit)
-- FRAMES CREATED:
--   MidnightUI_InspectPanel (main panel)
--   Equipment slot button frames (19 slots, inspected unit)
-- GOTCHAS:
--   INSPECT_READY fires asynchronously after NotifyInspect.
--   If the player moves too far from the target before data
--   arrives, GetInventoryItemLink for the unit returns nil.
--   Character model: PlayerModel:SetUnit requires the unit
--   to be in range and inspectable; fails silently otherwise.
-- ============================================================

local ADDON_NAME = "MidnightUI"
local W8 = "Interface\\Buttons\\WHITE8X8"
local TITLE_FONT = (_G.MidnightUI_ResolveFontPath and _G.MidnightUI_ResolveFontPath("Fonts\\FRIZQT__.TTF")) or "Fonts\\FRIZQT__.TTF"
local BODY_FONT  = (_G.MidnightUI_ResolveFontPath and _G.MidnightUI_ResolveFontPath("Fonts\\FRIZQT__.TTF")) or "Fonts\\FRIZQT__.TTF"

local function SafeCall(fn, ...)
    if type(fn) ~= "function" then return nil end
    local ok, a, b, c, d, e, f, g, h = pcall(fn, ...)
    if ok then return a, b, c, d, e, f, g, h end
    return nil
end

local function TrySetFont(fs, fontPath, size, flags)
    if not fs then return end
    local resolved = (_G.MidnightUI_ResolveFontPath and _G.MidnightUI_ResolveFontPath(fontPath or TITLE_FONT)) or (fontPath or TITLE_FONT)
    pcall(fs.SetFont, fs, resolved, size or 12, flags or "")
end

local function FormatNumber(n)
    if not n then return "0" end
    n = math.floor(tonumber(n) or 0)
    if n >= 1000000 then
        return string.format("%.1fM", n / 1000000)
    elseif n >= 1000 then
        local k = string.format("%d", n)
        local result = k:reverse():gsub("(%d%d%d)", "%1,"):reverse()
        return (result:gsub("^,", ""))
    end
    return tostring(n)
end

local THEMES = {
    parchment = {
        key         = "parchment",
        frameBg     = { 0.06, 0.06, 0.08, 0.96 },
        modelBg     = { 0.04, 0.05, 0.07, 1.0 },
        headerBg    = { 0.05, 0.05, 0.07, 0.96 },
        glassBg     = { 0.04, 0.04, 0.06, 0.70 },
        glassBorder = { 1, 1, 1, 0.08 },
        accent      = { 0.72, 0.62, 0.42 },
        titleText   = { 0.96, 0.87, 0.58 },
        bodyText    = { 0.94, 0.90, 0.80 },
        mutedText   = { 0.71, 0.62, 0.44 },
        divider     = { 0.60, 0.52, 0.35 },
        tabActive   = { 0.96, 0.87, 0.58 },
        tabInactive = { 0.52, 0.46, 0.34 },
    },
    midnight = {
        key         = "midnight",
        frameBg     = { 0.03, 0.04, 0.07, 0.97 },
        modelBg     = { 0.02, 0.025, 0.04, 1.0 },
        headerBg    = { 0.025, 0.03, 0.06, 0.95 },
        glassBg     = { 0.03, 0.04, 0.08, 0.70 },
        glassBorder = { 1, 1, 1, 0.06 },
        accent      = { 0.00, 0.78, 1.00 },
        titleText   = { 0.92, 0.93, 0.96 },
        bodyText    = { 0.82, 0.84, 0.88 },
        mutedText   = { 0.58, 0.60, 0.65 },
        divider     = { 0.35, 0.40, 0.50 },
        tabActive   = { 0.92, 0.93, 0.96 },
        tabInactive = { 0.45, 0.48, 0.55 },
    },
    class = {
        key         = "class",
        frameBg     = { 0.04, 0.035, 0.025, 0.97 },
        modelBg     = { 0.03, 0.03, 0.04, 1.0 },
        headerBg    = { 0.05, 0.04, 0.025, 0.95 },
        glassBg     = { 0.04, 0.04, 0.06, 0.70 },
        glassBorder = { 1, 1, 1, 0.08 },
        accent      = { 0.72, 0.62, 0.42 },
        titleText   = { 0.94, 0.92, 0.88 },
        bodyText    = { 0.92, 0.90, 0.85 },
        mutedText   = { 0.65, 0.62, 0.55 },
        divider     = { 0.50, 0.48, 0.40 },
        tabActive   = { 0.94, 0.92, 0.88 },
        tabInactive = { 0.50, 0.48, 0.42 },
    },
}

local activeTheme = THEMES.parchment

local function GetActiveTheme(unit)
    local s = _G.MidnightUISettings
    local key = (s and s.General and s.General.characterPanelTheme) or "parchment"
    local t = THEMES[key] or THEMES.parchment
    if key == "class" and unit then
        local cc = _G.MidnightUI_Core and _G.MidnightUI_Core.GetClassColorTable
            and _G.MidnightUI_Core.GetClassColorTable(unit)
        if cc then
            t.accent = { cc.r or cc[1] or 0.72, cc.g or cc[2] or 0.62, cc.b or cc[3] or 0.42 }
        end
    end
    activeTheme = t
    return t
end

local function TC(key)
    local c = activeTheme[key]
    if not c then return 1, 1, 1, 1 end
    return c[1], c[2], c[3], c[4] or 1
end

local CFG = {
    WIDTH           = 1060,
    HEIGHT          = 780,
    HEADER_H        = 44,
    TAB_BAR_H       = 0,
    LEFT_GLASS_W    = 220,
    RIGHT_GLASS_W   = 220,
    GLASS_PAD       = 10,
    GLASS_MARGIN    = 8,
    SLOT_SIZE       = 34,
    SLOT_GAP        = 4,
    IDENTITY_H      = 70,
    STRATA          = "HIGH",
    modelLight = { true, false, -0.4, 0.8, -0.5, 0.95, 0.82, 0.72, 0.58, 0.7, 1.0, 0.94, 0.84 },
}

local function CreateDropShadow(frame, intensity)
    intensity = intensity or 6
    local shadows = {}
    for i = 1, intensity do
        local s = CreateFrame("Frame", nil, frame)
        s:SetFrameLevel(math.max(0, frame:GetFrameLevel() - 1))
        local offset = i * 0.8
        local alpha = (0.18 - (i * 0.025)) * (intensity / 6)
        s:SetPoint("TOPLEFT", frame, "TOPLEFT", -offset, offset)
        s:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", offset, -offset)
        local t = s:CreateTexture(nil, "BACKGROUND")
        t:SetAllPoints()
        t:SetColorTexture(0, 0, 0, alpha)
        shadows[#shadows + 1] = s
    end
    return shadows
end

local function CreateGlassPanel(parent, width)
    local glass = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    glass:SetWidth(width)
    glass:SetBackdrop({
        bgFile = W8, edgeFile = W8, edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    glass:SetBackdropColor(TC("glassBg"))
    glass:SetBackdropBorderColor(TC("glassBorder"))

    local frost = glass:CreateTexture(nil, "OVERLAY", nil, 3)
    frost:SetHeight(1)
    frost:SetPoint("TOPLEFT", glass, "TOPLEFT", 1, -1)
    frost:SetPoint("TOPRIGHT", glass, "TOPRIGHT", -1, -1)
    frost:SetColorTexture(1, 1, 1, 0.10)

    local grad = glass:CreateTexture(nil, "BACKGROUND", nil, 1)
    grad:SetPoint("TOPLEFT", 1, -1)
    grad:SetPoint("BOTTOMRIGHT", -1, 1)
    grad:SetTexture(W8)
    if grad.SetGradient and CreateColor then
        grad:SetGradient("VERTICAL",
            CreateColor(0, 0, 0, 0.15),
            CreateColor(0, 0, 0, 0))
    end

    return glass
end

local function GetQualityColorRGB(quality)
    if not quality or quality < 0 then return 0.65, 0.65, 0.65 end
    local r, g, b = GetItemQualityColor(quality)
    if r then return r, g, b end
    return 0.65, 0.65, 0.65
end

local SLOT_LAYOUT = {
    { id = 1,  name = "HeadSlot",          label = "Head" },
    { id = 2,  name = "NeckSlot",          label = "Neck" },
    { id = 3,  name = "ShoulderSlot",      label = "Shoulders" },
    { id = 15, name = "BackSlot",          label = "Back" },
    { id = 5,  name = "ChestSlot",         label = "Chest" },
    { id = 9,  name = "WristSlot",         label = "Wrists" },
    { id = 10, name = "HandsSlot",         label = "Hands" },
    { id = 6,  name = "WaistSlot",         label = "Waist" },
    { id = 7,  name = "LegsSlot",          label = "Legs" },
    { id = 8,  name = "FeetSlot",          label = "Feet" },
    { id = 11, name = "Finger0Slot",       label = "Ring 1" },
    { id = 12, name = "Finger1Slot",       label = "Ring 2" },
    { id = 13, name = "Trinket0Slot",      label = "Trinket 1" },
    { id = 14, name = "Trinket1Slot",      label = "Trinket 2" },
    { id = 16, name = "MainHandSlot",      label = "Main Hand" },
    { id = 17, name = "SecondaryHandSlot", label = "Off Hand" },
}

local Panel = {}
Panel._state = {
    initialized   = false,
    panelOpen     = false,
    activeTab     = "character",
    modelFacing   = 0.4,
    modelZoom     = 1.0,
    isDragging    = false,
    dragStartX    = 0,
    dragStartFacing = 0,
    inspectUnit   = "target",
    inspectGUID   = nil,
    pendingShow   = false,
}
Panel._refs = {}

local function GetUnitInspectStats(unit)
    local stats = {}

    local _, classFile = SafeCall(UnitClass, unit)
    local PRIMARY_BY_CLASS = {
        WARRIOR = { 1, "Strength" },
        PALADIN = { 1, "Strength" },
        DEATHKNIGHT = { 1, "Strength" },
        HUNTER = { 2, "Agility" },
        ROGUE = { 2, "Agility" },
        MONK = { 2, "Agility" },
        DEMONHUNTER = { 2, "Agility" },
        DRUID = { 2, "Agility" },
        SHAMAN = { 4, "Intellect" },
        MAGE = { 4, "Intellect" },
        WARLOCK = { 4, "Intellect" },
        PRIEST = { 4, "Intellect" },
        EVOKER = { 4, "Intellect" },
    }
    local primary = classFile and PRIMARY_BY_CLASS[classFile]

    if primary then
        local _, statVal = SafeCall(UnitStat, unit, primary[1])
        if statVal and statVal > 0 then
            stats[#stats + 1] = { name = primary[2]:upper(), value = statVal, subs = {} }
        end
    end

    local _, stam = SafeCall(UnitStat, unit, 3)
    if stam and stam > 0 then
        stats[#stats + 1] = { name = "STAMINA", value = stam, subs = {} }
    end

    local _, eff = SafeCall(UnitArmor, unit)
    if eff and eff > 0 then
        stats[#stats + 1] = { name = "ARMOR", value = eff, subs = {} }
    end

    return stats
end

function Panel.EnsurePanel()
    if Panel._state.initialized then return Panel._refs.panel end
    Panel._state.initialized = true
    GetActiveTheme("player")

    local R = Panel._refs

    local p = CreateFrame("Frame", "MidnightUI_InspectPanel", UIParent, "BackdropTemplate")
    p:SetSize(CFG.WIDTH, CFG.HEIGHT)
    p:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
    p:SetFrameStrata(CFG.STRATA)
    p:SetBackdrop({ bgFile = W8, edgeFile = W8, edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 } })
    p:SetBackdropColor(TC("frameBg"))
    p:SetBackdropBorderColor(TC("accent"))
    p:Hide()
    R.panel = p

    CreateDropShadow(p, 5)

    local hdr = CreateFrame("Frame", nil, p)
    hdr:SetHeight(CFG.HEADER_H)
    hdr:SetPoint("TOPLEFT", p, "TOPLEFT", 0, 0)
    hdr:SetPoint("TOPRIGHT", p, "TOPRIGHT", 0, 0)
    local hdrBg = hdr:CreateTexture(nil, "BACKGROUND")
    hdrBg:SetAllPoints()
    hdrBg:SetColorTexture(TC("headerBg"))
    R.header = hdr

    local hdrAccent = hdr:CreateTexture(nil, "OVERLAY")
    hdrAccent:SetHeight(2)
    hdrAccent:SetPoint("BOTTOMLEFT", hdr, "BOTTOMLEFT", 0, 0)
    hdrAccent:SetPoint("BOTTOMRIGHT", hdr, "BOTTOMRIGHT", 0, 0)
    hdrAccent:SetTexture(W8)
    if hdrAccent.SetGradient and CreateColor then
        hdrAccent:SetGradient("HORIZONTAL",
            CreateColor(activeTheme.accent[1], activeTheme.accent[2], activeTheme.accent[3], 0.6),
            CreateColor(activeTheme.accent[1], activeTheme.accent[2], activeTheme.accent[3], 0.0))
    end

    local titleFS = hdr:CreateFontString(nil, "OVERLAY")
    TrySetFont(titleFS, TITLE_FONT, 16, "OUTLINE")
    titleFS:SetPoint("LEFT", hdr, "LEFT", 16, 0)
    titleFS:SetText("Inspect")
    titleFS:SetTextColor(TC("titleText"))
    R.titleFS = titleFS

    local closeBtn = CreateFrame("Button", nil, hdr)
    closeBtn:SetSize(28, 28)
    closeBtn:SetPoint("RIGHT", hdr, "RIGHT", -10, 0)
    local closeTx = closeBtn:CreateFontString(nil, "OVERLAY")
    TrySetFont(closeTx, TITLE_FONT, 16, "OUTLINE")
    closeTx:SetPoint("CENTER")
    closeTx:SetText("X")
    closeTx:SetTextColor(activeTheme.mutedText[1], activeTheme.mutedText[2], activeTheme.mutedText[3], 0.70)
    closeBtn:SetScript("OnEnter", function() closeTx:SetTextColor(TC("titleText")) end)
    closeBtn:SetScript("OnLeave", function() closeTx:SetTextColor(activeTheme.mutedText[1], activeTheme.mutedText[2], activeTheme.mutedText[3], 0.70) end)
    closeBtn:SetScript("OnClick", function() Panel.Hide() end)

    p:EnableMouse(true); p:SetMovable(true)
    hdr:EnableMouse(true); hdr:RegisterForDrag("LeftButton")
    hdr:SetScript("OnDragStart", function() p:StartMoving() end)
    hdr:SetScript("OnDragStop", function() p:StopMovingOrSizing() end)

    local modelArea = CreateFrame("Frame", nil, p)
    modelArea:SetPoint("TOPLEFT", hdr, "BOTTOMLEFT", 0, 0)
    modelArea:SetPoint("BOTTOMRIGHT", p, "BOTTOMRIGHT", 0, CFG.TAB_BAR_H)
    R.modelArea = modelArea

    local modelBgBase = modelArea:CreateTexture(nil, "BACKGROUND", nil, -8)
    modelBgBase:SetAllPoints()
    modelBgBase:SetColorTexture(0.02, 0.02, 0.03, 1)

    local modelBg = modelArea:CreateTexture(nil, "BACKGROUND", nil, -7)
    modelBg:SetAllPoints()
    local atlasOk = pcall(modelBg.SetAtlas, modelBg, "completiondialog-midnightcampaign-background", false)
    if not atlasOk then
        modelBg:SetColorTexture(TC("modelBg"))
    end
    modelBg:SetAlpha(0.7)

    local vigTop = modelArea:CreateTexture(nil, "BACKGROUND", nil, -5)
    vigTop:SetHeight(120)
    vigTop:SetPoint("TOPLEFT", modelArea, "TOPLEFT", 0, 0)
    vigTop:SetPoint("TOPRIGHT", modelArea, "TOPRIGHT", 0, 0)
    vigTop:SetTexture(W8)
    if vigTop.SetGradient and CreateColor then
        vigTop:SetGradient("VERTICAL", CreateColor(0, 0, 0, 0), CreateColor(0, 0, 0, 0.6))
    end
    local vigBot = modelArea:CreateTexture(nil, "BACKGROUND", nil, -5)
    vigBot:SetHeight(100)
    vigBot:SetPoint("BOTTOMLEFT", modelArea, "BOTTOMLEFT", 0, 0)
    vigBot:SetPoint("BOTTOMRIGHT", modelArea, "BOTTOMRIGHT", 0, 0)
    vigBot:SetTexture(W8)
    if vigBot.SetGradient and CreateColor then
        vigBot:SetGradient("VERTICAL", CreateColor(0, 0, 0, 0.7), CreateColor(0, 0, 0, 0))
    end

    local charModel = CreateFrame("PlayerModel", nil, modelArea)
    charModel:SetPoint("TOPLEFT", modelArea, "TOPLEFT", 0, 0)
    charModel:SetPoint("BOTTOMRIGHT", modelArea, "BOTTOMRIGHT", 0, 0)
    charModel:SetFrameLevel(modelArea:GetFrameLevel() + 2)
    R.charModel = charModel

    charModel:EnableMouse(true)
    charModel:SetScript("OnMouseDown", function(self, button)
        if button == "LeftButton" then
            Panel._state.isDragging = true
            Panel._state.dragStartX = GetCursorPosition()
            Panel._state.dragStartFacing = Panel._state.modelFacing
        end
    end)
    charModel:SetScript("OnMouseUp", function(self, button)
        if button == "LeftButton" then
            Panel._state.isDragging = false
        end
    end)
    charModel:SetScript("OnUpdate", function(self)
        if Panel._state.isDragging then
            local cx = GetCursorPosition()
            local delta = (cx - Panel._state.dragStartX) * 0.01
            Panel._state.modelFacing = Panel._state.dragStartFacing + delta
            pcall(self.SetFacing, self, Panel._state.modelFacing)
        end
    end)
    charModel:EnableMouseWheel(true)
    charModel:SetScript("OnMouseWheel", function(self, delta)
        local zoom = Panel._state.modelZoom - (delta * 0.1)
        zoom = math.max(0.5, math.min(2.0, zoom))
        Panel._state.modelZoom = zoom
        pcall(self.SetCamDistanceScale, self, zoom)
    end)

    local identityBar = CreateFrame("Frame", nil, modelArea, "BackdropTemplate")
    identityBar:SetHeight(CFG.IDENTITY_H)
    identityBar:SetPoint("BOTTOMLEFT", modelArea, "BOTTOMLEFT", CFG.LEFT_GLASS_W + CFG.GLASS_MARGIN, 0)
    identityBar:SetPoint("BOTTOMRIGHT", modelArea, "BOTTOMRIGHT", -(CFG.RIGHT_GLASS_W + CFG.GLASS_MARGIN), 0)
    identityBar:SetBackdrop({ bgFile = W8, edgeFile = W8, edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 } })
    identityBar:SetBackdropColor(0, 0, 0, 0.55)
    identityBar:SetBackdropBorderColor(1, 1, 1, 0.05)
    identityBar:SetFrameLevel(charModel:GetFrameLevel() + 5)
    R.identityBar = identityBar

    local nameFS = identityBar:CreateFontString(nil, "OVERLAY")
    TrySetFont(nameFS, TITLE_FONT, 18, "OUTLINE")
    nameFS:SetPoint("TOP", identityBar, "TOP", 0, -10)
    nameFS:SetTextColor(TC("titleText"))
    nameFS:SetShadowColor(0, 0, 0, 0.9)
    nameFS:SetShadowOffset(2, -2)
    R.nameFS = nameFS

    local specFS = identityBar:CreateFontString(nil, "OVERLAY")
    TrySetFont(specFS, BODY_FONT, 11, "")
    specFS:SetPoint("TOP", nameFS, "BOTTOM", 0, -4)
    specFS:SetTextColor(TC("bodyText"))
    specFS:SetShadowColor(0, 0, 0, 0.8)
    specFS:SetShadowOffset(1, -1)
    R.specFS = specFS

    local btnRow = CreateFrame("Frame", nil, identityBar)
    btnRow:SetHeight(20)
    btnRow:SetPoint("BOTTOM", identityBar, "BOTTOM", 0, 6)
    btnRow:SetWidth(280)
    R.btnRow = btnRow

    local function MakeActionButton(label, parent, anchorTo, anchorSide)
        local b = CreateFrame("Button", nil, parent, "BackdropTemplate")
        b:SetSize(130, 20)
        b:SetBackdrop({ bgFile = W8, edgeFile = W8, edgeSize = 1,
            insets = { left = 1, right = 1, top = 1, bottom = 1 } })
        b:SetBackdropColor(0.04, 0.05, 0.07, 0.85)
        b:SetBackdropBorderColor(activeTheme.accent[1], activeTheme.accent[2], activeTheme.accent[3], 0.55)
        local tx = b:CreateFontString(nil, "OVERLAY")
        TrySetFont(tx, BODY_FONT, 11, "OUTLINE")
        tx:SetPoint("CENTER")
        tx:SetText(label)
        tx:SetTextColor(TC("titleText"))
        b:SetScript("OnEnter", function(self)
            self:SetBackdropColor(activeTheme.accent[1] * 0.3, activeTheme.accent[2] * 0.3, activeTheme.accent[3] * 0.3, 0.85)
        end)
        b:SetScript("OnLeave", function(self)
            self:SetBackdropColor(0.04, 0.05, 0.07, 0.85)
        end)
        return b, tx
    end

    local dressBtn = MakeActionButton("View in Dressing Room", btnRow)
    dressBtn:SetPoint("LEFT", btnRow, "LEFT", 0, 0)
    dressBtn:SetWidth(160)
    dressBtn:SetScript("OnClick", function()
        local unit = Panel._state.inspectUnit
        if not unit or not UnitExists(unit) then
            return
        end
        if SetDressUpBackground and DressUpFrame then
            local _, raceFile = SafeCall(UnitRace, unit)
            local _, classFile = SafeCall(UnitClass, unit)
            pcall(SetDressUpBackground, DressUpFrame, raceFile, classFile)
        end
        for slot = 1, 19 do
            local link = SafeCall(GetInventoryItemLink, unit, slot)
            if link then
                pcall(DressUpItemLink, link)
            end
        end
    end)
    R.dressBtn = dressBtn

    local talentBtn = MakeActionButton("Talents", btnRow)
    talentBtn:SetPoint("LEFT", dressBtn, "RIGHT", 8, 0)
    talentBtn:SetWidth(110)
    talentBtn:SetScript("OnClick", function()
        local unit = Panel._state.inspectUnit
        if not unit or not UnitExists(unit) then
            return
        end
        if not C_AddOns or not C_AddOns.IsAddOnLoaded then
            return
        end
        local inspectLoaded = C_AddOns.IsAddOnLoaded("Blizzard_InspectUI")
        local talentLoaded  = C_AddOns.IsAddOnLoaded("Blizzard_TalentUI")
        if not inspectLoaded then
            pcall(C_AddOns.LoadAddOn, "Blizzard_InspectUI")
        end
        if not talentLoaded then
            pcall(C_AddOns.LoadAddOn, "Blizzard_TalentUI")
        end
        Panel._state._allowBlizzInspectOnce = true

        Panel.Hide()

        if _G.InspectFrame_Show then
            pcall(_G.InspectFrame_Show, unit)
        elseif _G.NotifyInspect then
            pcall(_G.NotifyInspect, unit)
        end

        if C_Timer and C_Timer.After then
            C_Timer.After(0.05, function()
                local talentTab
                for i = 1, 5 do
                    local tab = _G["InspectFrameTab" .. i]
                    if tab then
                        local txt = (tab.Text and tab.Text:GetText())
                            or (tab.GetText and tab:GetText())
                            or ""
                        if type(txt) == "string"
                            and txt:lower():find("talent") then
                            talentTab = tab
                            break
                        end
                    end
                end
                if talentTab and talentTab.Click then
                    pcall(talentTab.Click, talentTab)
                end
            end)
        end
    end)
    R.talentBtn = talentBtn

    local leftGlass = CreateGlassPanel(modelArea, CFG.LEFT_GLASS_W)
    leftGlass:SetPoint("TOPLEFT", modelArea, "TOPLEFT", CFG.GLASS_MARGIN, -CFG.GLASS_MARGIN)
    leftGlass:SetPoint("BOTTOMLEFT", modelArea, "BOTTOMLEFT", CFG.GLASS_MARGIN, CFG.GLASS_MARGIN)
    leftGlass:SetFrameLevel(charModel:GetFrameLevel() + 3)
    R.leftGlass = leftGlass

    local gearLabel = leftGlass:CreateFontString(nil, "OVERLAY")
    TrySetFont(gearLabel, BODY_FONT, 10, "OUTLINE")
    gearLabel:SetPoint("TOPLEFT", leftGlass, "TOPLEFT", CFG.GLASS_PAD, -CFG.GLASS_PAD)
    gearLabel:SetText("EQUIPMENT")
    gearLabel:SetTextColor(TC("mutedText"))

    local gearSlots = {}
    local GEAR_ROW_H = 36
    local GEAR_ROW_GAP = 2
    local GEAR_ICON_SIZE = 32

    for i, slotDef in ipairs(SLOT_LAYOUT) do
        local slotID = slotDef.id

        local btn = CreateFrame("Button", nil, leftGlass)
        btn:SetHeight(GEAR_ROW_H)
        local yOfs = -(CFG.GLASS_PAD + 16 + (i - 1) * (GEAR_ROW_H + GEAR_ROW_GAP))
        btn:SetPoint("TOPLEFT", leftGlass, "TOPLEFT", 0, yOfs)
        btn:SetPoint("TOPRIGHT", leftGlass, "TOPRIGHT", 0, yOfs)

        local hoverBg = btn:CreateTexture(nil, "BACKGROUND")
        hoverBg:SetAllPoints()
        hoverBg:SetColorTexture(1, 1, 1, 0.05)
        hoverBg:Hide()
        btn._hoverBg = hoverBg

        local icon = btn:CreateTexture(nil, "ARTWORK")
        icon:SetSize(GEAR_ICON_SIZE, GEAR_ICON_SIZE)
        icon:SetPoint("LEFT", btn, "LEFT", CFG.GLASS_PAD, 0)
        icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
        btn._icon = icon

        local bTop = btn:CreateTexture(nil, "OVERLAY"); bTop:SetHeight(1)
        bTop:SetPoint("TOPLEFT", icon, "TOPLEFT", -1, 1); bTop:SetPoint("TOPRIGHT", icon, "TOPRIGHT", 1, 1)
        bTop:SetColorTexture(0.3, 0.3, 0.3, 0.5)
        local bBot = btn:CreateTexture(nil, "OVERLAY"); bBot:SetHeight(1)
        bBot:SetPoint("BOTTOMLEFT", icon, "BOTTOMLEFT", -1, -1); bBot:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", 1, -1)
        bBot:SetColorTexture(0.3, 0.3, 0.3, 0.5)
        local bLeft = btn:CreateTexture(nil, "OVERLAY"); bLeft:SetWidth(1)
        bLeft:SetPoint("TOPLEFT", icon, "TOPLEFT", -1, 1); bLeft:SetPoint("BOTTOMLEFT", icon, "BOTTOMLEFT", -1, -1)
        bLeft:SetColorTexture(0.3, 0.3, 0.3, 0.5)
        local bRight = btn:CreateTexture(nil, "OVERLAY"); bRight:SetWidth(1)
        bRight:SetPoint("TOPRIGHT", icon, "TOPRIGHT", 1, 1); bRight:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", 1, -1)
        bRight:SetColorTexture(0.3, 0.3, 0.3, 0.5)
        btn._borderParts = { bTop, bBot, bLeft, bRight }

        local slotLabelFS = btn:CreateFontString(nil, "OVERLAY")
        TrySetFont(slotLabelFS, BODY_FONT, 10, "")
        slotLabelFS:SetPoint("TOPLEFT", icon, "TOPRIGHT", 6, -1)
        slotLabelFS:SetText(slotDef.label)
        slotLabelFS:SetTextColor(activeTheme.mutedText[1], activeTheme.mutedText[2], activeTheme.mutedText[3], 0.6)
        btn._slotLabel = slotLabelFS

        local itemNameFS = btn:CreateFontString(nil, "OVERLAY")
        TrySetFont(itemNameFS, BODY_FONT, 10, "")
        itemNameFS:SetPoint("BOTTOMLEFT", icon, "BOTTOMRIGHT", 6, 2)
        itemNameFS:SetPoint("RIGHT", btn, "RIGHT", -CFG.GLASS_PAD, 0)
        itemNameFS:SetJustifyH("LEFT")
        itemNameFS:SetWordWrap(false)
        itemNameFS:SetTextColor(TC("bodyText"))
        btn._itemName = itemNameFS

        local ilvlFS = btn:CreateFontString(nil, "OVERLAY")
        TrySetFont(ilvlFS, BODY_FONT, 9, "OUTLINE")
        ilvlFS:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", -1, 1)
        ilvlFS:SetJustifyH("RIGHT")
        ilvlFS:SetTextColor(1, 1, 1, 0.9)
        btn._ilvl = ilvlFS

        btn._slotID = slotID

        btn:SetScript("OnEnter", function(self)
            self._hoverBg:Show()
            local unit = Panel._state.inspectUnit
            if unit and UnitExists(unit) then
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                pcall(GameTooltip.SetInventoryItem, GameTooltip, unit, self._slotID)
                GameTooltip:Show()
            end
        end)
        btn:SetScript("OnLeave", function(self) self._hoverBg:Hide(); GameTooltip:Hide() end)

        gearSlots[slotID] = btn
    end
    R.gearSlots = gearSlots

    local rightGlass = CreateGlassPanel(modelArea, CFG.RIGHT_GLASS_W)
    rightGlass:SetPoint("TOPRIGHT", modelArea, "TOPRIGHT", -CFG.GLASS_MARGIN, -CFG.GLASS_MARGIN)
    rightGlass:SetPoint("BOTTOMRIGHT", modelArea, "BOTTOMRIGHT", -CFG.GLASS_MARGIN, CFG.GLASS_MARGIN)
    rightGlass:SetFrameLevel(charModel:GetFrameLevel() + 3)
    R.rightGlass = rightGlass

    local ilvlLabel = rightGlass:CreateFontString(nil, "OVERLAY")
    TrySetFont(ilvlLabel, BODY_FONT, 10, "OUTLINE")
    ilvlLabel:SetPoint("TOP", rightGlass, "TOP", 0, -18)
    ilvlLabel:SetText("ITEM LEVEL")
    ilvlLabel:SetTextColor(TC("mutedText"))

    local ilvlValue = rightGlass:CreateFontString(nil, "OVERLAY")
    TrySetFont(ilvlValue, TITLE_FONT, 28, "OUTLINE")
    ilvlValue:SetPoint("TOP", ilvlLabel, "BOTTOM", 0, -4)
    ilvlValue:SetTextColor(TC("titleText"))
    ilvlValue:SetShadowColor(0, 0, 0, 0.8)
    ilvlValue:SetShadowOffset(2, -2)
    R.ilvlValue = ilvlValue

    R.attrLabel = ilvlValue

    local MAX_ATTR = 4
    local attrRows = {}
    for i = 1, MAX_ATTR do
        local nameFS2 = rightGlass:CreateFontString(nil, "OVERLAY")
        TrySetFont(nameFS2, BODY_FONT, 11, "OUTLINE")
        nameFS2:SetTextColor(TC("accent"))
        nameFS2:SetJustifyH("LEFT")

        local valFS = rightGlass:CreateFontString(nil, "OVERLAY")
        TrySetFont(valFS, BODY_FONT, 11, "")
        valFS:SetTextColor(TC("titleText"))
        valFS:SetJustifyH("RIGHT")

        nameFS2:Hide(); valFS:Hide()
        attrRows[i] = { name = nameFS2, value = valFS }
    end
    R.attrRows = attrRows

    local function MakeSectionSeparator()
        local sep = rightGlass:CreateTexture(nil, "OVERLAY")
        sep:SetHeight(1)
        sep:SetPoint("LEFT", rightGlass, "LEFT", CFG.GLASS_PAD, 0)
        sep:SetPoint("RIGHT", rightGlass, "RIGHT", -CFG.GLASS_PAD, 0)
        sep:SetTexture(W8)
        if sep.SetGradient and CreateColor then
            sep:SetGradient("HORIZONTAL",
                CreateColor(activeTheme.accent[1], activeTheme.accent[2], activeTheme.accent[3], 0.3),
                CreateColor(activeTheme.accent[1], activeTheme.accent[2], activeTheme.accent[3], 0))
        end
        sep:Hide()
        return sep
    end

    local pvpSep = MakeSectionSeparator()
    R.pvpSep = pvpSep

    local pvpLabel = rightGlass:CreateFontString(nil, "OVERLAY")
    TrySetFont(pvpLabel, BODY_FONT, 10, "OUTLINE")
    pvpLabel:SetText("PVP")
    pvpLabel:SetTextColor(TC("mutedText"))
    pvpLabel:Hide()
    R.pvpLabel = pvpLabel

    local MAX_PVP = 8
    local pvpRows = {}
    for i = 1, MAX_PVP do
        local nameFS2 = rightGlass:CreateFontString(nil, "OVERLAY")
        TrySetFont(nameFS2, BODY_FONT, 11, "OUTLINE")
        nameFS2:SetTextColor(TC("accent"))
        nameFS2:SetJustifyH("LEFT")

        local valFS = rightGlass:CreateFontString(nil, "OVERLAY")
        TrySetFont(valFS, BODY_FONT, 11, "")
        valFS:SetTextColor(TC("titleText"))
        valFS:SetJustifyH("RIGHT")

        nameFS2:Hide(); valFS:Hide()
        pvpRows[i] = { name = nameFS2, value = valFS }
    end
    R.pvpRows = pvpRows

    local guildSep = MakeSectionSeparator()
    R.guildSep = guildSep

    local guildLabel = rightGlass:CreateFontString(nil, "OVERLAY")
    TrySetFont(guildLabel, BODY_FONT, 10, "OUTLINE")
    guildLabel:SetText("GUILD")
    guildLabel:SetTextColor(TC("mutedText"))
    guildLabel:Hide()
    R.guildLabel = guildLabel

    local MAX_GUILD = 4
    local guildRows = {}
    for i = 1, MAX_GUILD do
        local nameFS2 = rightGlass:CreateFontString(nil, "OVERLAY")
        TrySetFont(nameFS2, BODY_FONT, 11, "OUTLINE")
        nameFS2:SetTextColor(TC("accent"))
        nameFS2:SetJustifyH("LEFT")

        local valFS = rightGlass:CreateFontString(nil, "OVERLAY")
        TrySetFont(valFS, BODY_FONT, 11, "")
        valFS:SetTextColor(TC("titleText"))
        valFS:SetJustifyH("RIGHT")
        valFS:SetWordWrap(false)

        nameFS2:Hide(); valFS:Hide()
        guildRows[i] = { name = nameFS2, value = valFS }
    end
    R.guildRows = guildRows

    local guildEmptyFS = rightGlass:CreateFontString(nil, "OVERLAY")
    TrySetFont(guildEmptyFS, BODY_FONT, 11, "")
    guildEmptyFS:SetTextColor(TC("mutedText"))
    guildEmptyFS:SetJustifyH("LEFT")
    guildEmptyFS:Hide()
    R.guildEmptyFS = guildEmptyFS

    return p
end

function Panel.UpdateModel()
    local R = Panel._refs
    if not R.charModel then return end
    local unit = Panel._state.inspectUnit
    R.charModel:ClearModel()
    if unit and UnitExists(unit) then
        pcall(R.charModel.SetUnit, R.charModel, unit)
    end
    pcall(R.charModel.SetPortraitZoom, R.charModel, 0)
    pcall(R.charModel.SetFacing, R.charModel, Panel._state.modelFacing)
    pcall(R.charModel.SetPosition, R.charModel, 0, 0, -0.15)
    pcall(R.charModel.SetCamDistanceScale, R.charModel, Panel._state.modelZoom)
    local L = CFG.modelLight
    pcall(R.charModel.SetLight, R.charModel,
        L[1], L[2], L[3], L[4], L[5], L[6], L[7], L[8], L[9], L[10], L[11], L[12], L[13])
end

function Panel.UpdateIdentity()
    local R = Panel._refs
    if not R.nameFS then return end
    local unit = Panel._state.inspectUnit

    local name = SafeCall(UnitName, unit) or "Unknown"
    R.nameFS:SetText(name)

    local className = SafeCall(UnitClass, unit) or ""
    local level = SafeCall(UnitLevel, unit) or 0
    local race = SafeCall(UnitRace, unit) or ""
    local levelText = (level and level > 0) and ("Level " .. level) or "Level ??"
    local sep = "  \194\183  "
    local parts = {}
    if race ~= "" then parts[#parts + 1] = race end
    if className ~= "" then parts[#parts + 1] = className end
    parts[#parts + 1] = levelText
    R.specFS:SetText(table.concat(parts, sep))
end

function Panel.UpdateGearSlots()
    local R = Panel._refs
    if not R.gearSlots then return end
    local unit = Panel._state.inspectUnit
    if not unit or not UnitExists(unit) then return end

    for slotID, btn in pairs(R.gearSlots) do
        local tex = SafeCall(GetInventoryItemTexture, unit, slotID)
        if tex then
            btn._icon:SetTexture(tex)
            btn._icon:SetVertexColor(1, 1, 1, 1)
            local link = SafeCall(GetInventoryItemLink, unit, slotID)
            if link then
                local itemName, _, quality = SafeCall(GetItemInfo, link)
                local r, g, b = GetQualityColorRGB(quality or 1)
                for _, part in ipairs(btn._borderParts) do
                    part:SetColorTexture(r, g, b, 0.8)
                end
                if btn._itemName then
                    btn._itemName:SetText(itemName or "")
                    btn._itemName:SetTextColor(r, g, b)
                end
                if btn._slotLabel then
                    btn._slotLabel:SetTextColor(activeTheme.mutedText[1], activeTheme.mutedText[2], activeTheme.mutedText[3], 0.7)
                end
                local ilvl = nil
                if C_Item and C_Item.GetDetailedItemLevelInfo then
                    ilvl = SafeCall(C_Item.GetDetailedItemLevelInfo, link)
                end
                btn._ilvl:SetText(ilvl and tostring(ilvl) or "")
                btn._ilvl:SetTextColor(TC("mutedText"))
            else
                if btn._itemName then btn._itemName:SetText("") end
                btn._ilvl:SetText("")
            end
        else
            btn._icon:SetTexture("Interface\\PaperDoll\\UI-Backpack-EmptySlot")
            btn._icon:SetVertexColor(1, 1, 1, 0.15)
            btn._icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
            for _, part in ipairs(btn._borderParts) do
                part:SetColorTexture(0.3, 0.3, 0.3, 0.1)
            end
            if btn._itemName then
                btn._itemName:SetText("Empty")
                btn._itemName:SetTextColor(activeTheme.mutedText[1], activeTheme.mutedText[2], activeTheme.mutedText[3], 0.3)
            end
            if btn._slotLabel then
                btn._slotLabel:SetTextColor(activeTheme.mutedText[1], activeTheme.mutedText[2], activeTheme.mutedText[3], 0.4)
            end
            btn._ilvl:SetText("")
        end
    end
end

function Panel.UpdateItemLevel()
    local R = Panel._refs
    if not R.ilvlValue then return end
    local unit = Panel._state.inspectUnit
    if not unit or not UnitExists(unit) then
        R.ilvlValue:SetText("?")
        return
    end
    local ilvl
    if C_PaperDollInfo and C_PaperDollInfo.GetInspectItemLevel then
        ilvl = SafeCall(C_PaperDollInfo.GetInspectItemLevel, unit)
    end
    if not ilvl or ilvl == 0 then
        local total, count = 0, 0
        for _, slotDef in ipairs(SLOT_LAYOUT) do
            local link = SafeCall(GetInventoryItemLink, unit, slotDef.id)
            if link and C_Item and C_Item.GetDetailedItemLevelInfo then
                local lv = SafeCall(C_Item.GetDetailedItemLevelInfo, link)
                if lv and lv > 0 then total = total + lv; count = count + 1 end
            end
        end
        if count > 0 then ilvl = total / count end
    end
    R.ilvlValue:SetText(ilvl and tostring(math.floor((ilvl or 0) + 0.5)) or "?")
end


local function PlaceRow(row, anchor, gap, rightGlass, pad)
    row.name:ClearAllPoints()
    row.name:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, gap)
    row.name:SetPoint("LEFT", rightGlass, "LEFT", pad, 0)
    row.value:ClearAllPoints()
    row.value:SetPoint("RIGHT", rightGlass, "RIGHT", -pad, 0)
    row.value:SetPoint("TOP", row.name, "TOP", 0, 0)
    row.name:Show(); row.value:Show()
end

local function PlaceSectionLabel(label, anchor, gap, rightGlass, pad)
    label:ClearAllPoints()
    label:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, gap)
    label:SetPoint("LEFT", rightGlass, "LEFT", pad, 0)
    label:Show()
end

local function PlaceSectionSeparator(sep, anchor, gap, rightGlass, pad)
    sep:ClearAllPoints()
    sep:SetPoint("LEFT", rightGlass, "LEFT", pad, 0)
    sep:SetPoint("RIGHT", rightGlass, "RIGHT", -pad, 0)
    sep:SetPoint("TOP", anchor, "BOTTOM", 0, gap)
    sep:Show()
end

function Panel.UpdatePrimaryStats()
    local R = Panel._refs
    if not R.attrRows then return end
    local unit = Panel._state.inspectUnit
    local PAD = CFG.GLASS_PAD

    for i = 1, #R.attrRows do
        R.attrRows[i].name:Hide(); R.attrRows[i].value:Hide()
    end

    if not unit or not UnitExists(unit) then
        Panel._state._lastAttrAnchor = R.attrLabel
        return
    end

    local stats = GetUnitInspectStats(unit)
    local lastAnchor = R.attrLabel
    for i, data in ipairs(stats) do
        local row = R.attrRows[i]
        if not row then break end
        row.name:SetText(data.name)
        row.value:SetText(FormatNumber(data.value))
        PlaceRow(row, lastAnchor, (i == 1) and -8 or -12, R.rightGlass, PAD)
        lastAnchor = row.name
    end
    Panel._state._lastAttrAnchor = lastAnchor
end

function Panel.UpdatePVP()
    local R = Panel._refs
    if not R.pvpRows then return end
    local unit = Panel._state.inspectUnit
    local PAD = CFG.GLASS_PAD

    for i = 1, #R.pvpRows do
        R.pvpRows[i].name:Hide(); R.pvpRows[i].value:Hide()
    end
    if R.pvpSep then R.pvpSep:Hide() end
    if R.pvpLabel then R.pvpLabel:Hide() end

    local anchor = Panel._state._lastAttrAnchor or R.attrLabel
    Panel._state._lastPVPAnchor = anchor

    if not unit or not UnitExists(unit) then return end

    local rows = {}

    local honorLevel = SafeCall(UnitHonorLevel, unit) or 0
    rows[#rows + 1] = { label = "Honor Level", value = tostring(honorLevel) }

    local hk
    if GetPVPLifetimeStats then
        hk = SafeCall(GetPVPLifetimeStats)
    end
    if hk then
        rows[#rows + 1] = { label = "Honorable Kills", value = FormatNumber(hk) }
    end

    local BRACKETS = {
        { name = "2v2 Arena",      idx = 1 },
        { name = "3v3 Arena",      idx = 2 },
        { name = "Rated BG",       idx = 4 },
        { name = "Solo Shuffle",   idx = 7 },
        { name = "Rated BG Blitz", idx = 9 },
    }
    for _, b in ipairs(BRACKETS) do
        local rating = "—"
        if C_PvP and C_PvP.GetInspectRatedInfo then
            local info = SafeCall(C_PvP.GetInspectRatedInfo, unit, b.idx)
            if info and info.rating and info.rating > 0 then
                rating = tostring(info.rating)
            end
        end
        rows[#rows + 1] = { label = b.name, value = rating }
    end

    PlaceSectionSeparator(R.pvpSep, anchor, -14, R.rightGlass, PAD)
    PlaceSectionLabel(R.pvpLabel, R.pvpSep, -8, R.rightGlass, PAD)
    local lastAnchor = R.pvpLabel
    for i, data in ipairs(rows) do
        local row = R.pvpRows[i]
        if not row then break end
        row.name:SetText(data.label)
        row.value:SetText(data.value)
        PlaceRow(row, lastAnchor, (i == 1) and -8 or -6, R.rightGlass, PAD)
        lastAnchor = row.name
    end
    Panel._state._lastPVPAnchor = lastAnchor
end

function Panel.UpdateGuild()
    local R = Panel._refs
    if not R.guildRows then return end
    local unit = Panel._state.inspectUnit
    local PAD = CFG.GLASS_PAD

    for i = 1, #R.guildRows do
        R.guildRows[i].name:Hide(); R.guildRows[i].value:Hide()
    end
    if R.guildSep then R.guildSep:Hide() end
    if R.guildLabel then R.guildLabel:Hide() end
    if R.guildEmptyFS then R.guildEmptyFS:Hide() end

    local anchor = Panel._state._lastPVPAnchor
        or Panel._state._lastAttrAnchor
        or R.attrLabel

    if not unit or not UnitExists(unit) then return end

    PlaceSectionSeparator(R.guildSep, anchor, -14, R.rightGlass, PAD)
    PlaceSectionLabel(R.guildLabel, R.guildSep, -8, R.rightGlass, PAD)

    local guildName, guildRank, _, guildRealm = SafeCall(GetGuildInfo, unit)
    if not guildName or guildName == "" then
        R.guildEmptyFS:SetText("Not in a guild")
        R.guildEmptyFS:ClearAllPoints()
        R.guildEmptyFS:SetPoint("TOPLEFT", R.guildLabel, "BOTTOMLEFT", 0, -8)
        R.guildEmptyFS:SetPoint("LEFT", R.rightGlass, "LEFT", PAD, 0)
        R.guildEmptyFS:Show()
        return
    end


    local faction = SafeCall(UnitFactionGroup, unit)
    local factionLabel
    if faction == "Alliance" then factionLabel = "Alliance Guild"
    elseif faction == "Horde" then factionLabel = "Horde Guild" end

    local rows = {}
    rows[#rows + 1] = { label = "Guild", value = guildName }
    if guildRank and guildRank ~= "" then
        rows[#rows + 1] = { label = "Rank", value = guildRank }
    end
    if guildRealm and guildRealm ~= "" then
        rows[#rows + 1] = { label = "Realm", value = guildRealm }
    end
    if factionLabel then
        rows[#rows + 1] = { label = "Faction", value = factionLabel }
    end

    local lastAnchor = R.guildLabel
    for i, data in ipairs(rows) do
        local row = R.guildRows[i]
        if not row then break end
        row.name:SetText(data.label)
        row.value:SetText(data.value)
        PlaceRow(row, lastAnchor, (i == 1) and -8 or -6, R.rightGlass, PAD)
        lastAnchor = row.name
    end
end

function Panel.SetActiveTab(key)
    Panel._state.activeTab = key or "character"
end

function Panel.RefreshAll()
    Panel.UpdateModel()
    Panel.UpdateIdentity()
    Panel.UpdateGearSlots()
    Panel.UpdateItemLevel()
    Panel.UpdatePrimaryStats()
    Panel.UpdatePVP()
    Panel.UpdateGuild()
end

function Panel.RefreshDataOnly()
    Panel.UpdateIdentity()
    Panel.UpdateGearSlots()
    Panel.UpdateItemLevel()
    Panel.UpdatePrimaryStats()
    Panel.UpdatePVP()
    Panel.UpdateGuild()
end

function Panel.Show(unit)
    if unit then Panel._state.inspectUnit = unit end
    local activeUnit = Panel._state.inspectUnit
    if not activeUnit or not UnitExists(activeUnit) then return end

    local p = Panel.EnsurePanel()
    if not p then return end

    local cpHide = _G.MidnightUI_CharacterPanel_Hide
    if cpHide then cpHide() end
    local gfHide = _G.MidnightUI_GroupFinder_Hide
    if gfHide then gfHide() end

    GetActiveTheme(activeUnit)
    Panel._state.panelOpen = true
    Panel._state.inspectGUID = SafeCall(UnitGUID, activeUnit)
    p:Show()

    Panel.RefreshAll()
    Panel.SetActiveTab("character")

    if NotifyInspect and CanInspect and CanInspect(activeUnit) then
        pcall(NotifyInspect, activeUnit)
    end

    if C_Timer and C_Timer.After then
        C_Timer.After(0.3, function()
            if Panel._state.panelOpen then Panel.RefreshDataOnly() end
        end)
        C_Timer.After(0.8, function()
            if Panel._state.panelOpen then Panel.RefreshDataOnly() end
        end)
        C_Timer.After(1.5, function()
            if Panel._state.panelOpen then Panel.RefreshDataOnly() end
        end)
    end
end

function Panel.Hide()
    local R = Panel._refs
    if not R.panel then return end
    R.panel:Hide()
    Panel._state.panelOpen = false
    if origClearInspectPlayer then
        pcall(origClearInspectPlayer)
    end
end

function Panel.Toggle(unit)
    if Panel._state.panelOpen then
        Panel.Hide()
    else
        Panel.Show(unit)
    end
end

function Panel.IsOpen()
    return Panel._state.panelOpen
end

local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("INSPECT_READY")
eventFrame:SetScript("OnEvent", function(_, event, ...)
    if event == "INSPECT_READY" then
        if Panel._state.panelOpen then
            Panel.RefreshDataOnly()
        end
    end
end)

local inspectHookInstalled = false
local origClearInspectPlayer = nil
local function InstallInspectHook()
    if inspectHookInstalled then return end
    if not _G.InspectFrame then return end
    inspectHookInstalled = true

    if _G.ClearInspectPlayer and not origClearInspectPlayer then
        origClearInspectPlayer = _G.ClearInspectPlayer
        _G.ClearInspectPlayer = function(...)
            if Panel._state.panelOpen or Panel._state.suppressClearInspect then
                return
            end
            return origClearInspectPlayer(...)
        end
    end

    _G.InspectFrame:HookScript("OnShow", function(self)
        if Panel._state._allowBlizzInspectOnce then
            Panel._state._allowBlizzInspectOnce = nil
            return
        end
        local unit = self.unit or _G.InspectFrame.unit or "target"
        if not UnitExists(unit) then unit = "target" end
        Panel._state.suppressClearInspect = true
        HideUIPanel(self)
        Panel._state.suppressClearInspect = false
        Panel.Show(unit)
    end)
end

InstallInspectHook()
local hookEvf = CreateFrame("Frame")
hookEvf:RegisterEvent("ADDON_LOADED")
hookEvf:SetScript("OnEvent", function(_, event, addon)
    if event == "ADDON_LOADED" and addon == "Blizzard_InspectUI" then
        InstallInspectHook()
    end
end)

_G.MidnightUI_InspectPanel_Show   = function(unit) Panel.Show(unit) end
_G.MidnightUI_InspectPanel_Hide   = function() Panel.Hide() end
_G.MidnightUI_InspectPanel_Toggle = function(unit) Panel.Toggle(unit) end
_G.MidnightUI_InspectPanel_IsOpen = function() return Panel.IsOpen() end
