local addonName, SinsToolkit = ...

local DevTools = SinsToolkit.DevTools
if not DevTools then return end

local Inspector = {}
DevTools.FrameInspector = Inspector

local selection
local frozen = false
local lastReport = ""
local lastName = ""
local highlightFrame
local highlightActive = false
local hoverFrame
local hoverOverlay
local hoverText
local hoverActive = false
local hoverElapsed = 0
local hoverLastFrame
local keyCaptureFrame
local keyCaptureText
local keyCaptureCallback
local GetCopyBindingText

_G.BINDING_NAME_SINSTOOLKIT_COPY_FRAME_INSPECTOR = "Copy Frame Inspector Current"

local function Print(message)
    print("|cff9b59b6[Sin's Toolkit: QOL]|r " .. message)
end

StaticPopupDialogs["SinsToolkitFrameInspectorCopy"] = {
    text = "%s\nCTRL-C to copy",
    button1 = "Close",
    OnShow = function(self, data)
        self:SetFrameStrata("FULLSCREEN_DIALOG")
        self:SetFrameLevel(1000)
        local editBox = self.EditBox or self.editBox
        if not editBox then return end
        local text = type(data) == "table" and data.text or data
        editBox:SetMaxLetters(0)
        editBox:SetText(text or "")
        editBox:HighlightText()
        editBox:SetScript("OnEscapePressed", function(box) box:GetParent():Hide() end)
        editBox:SetScript("OnEnterPressed", function(box) box:GetParent():Hide() end)
        editBox:SetScript("OnKeyUp", function(box, key)
            local shouldCloseAfterCopy = type(data) == "table" and data.stopHoverAfterClose
            if shouldCloseAfterCopy
                and ((IsControlKeyDown() and key == "C") or (IsKeyDown and IsKeyDown("C") and (key == "LCTRL" or key == "RCTRL"))) then
                box:GetParent():Hide()
            end
        end)
    end,
    OnHide = function(_, data)
        if type(data) == "table" and data.stopHoverAfterClose then
            local inspector = SinsToolkit.DevTools and SinsToolkit.DevTools.FrameInspector
            if inspector and inspector.StopHoverInspector then
                inspector:StopHoverInspector(true)
            end
        end
    end,
    hasEditBox = true,
    editBoxWidth = 520,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
    preferredIndex = 3,
}

StaticPopupDialogs["SinsToolkitFrameInspectorReplaceBinding"] = {
    text = "%s is currently assigned to:\n\n%s\n\nReplace this binding with\nFrame Inspector Copy?",
    button1 = "Replace",
    button2 = "Cancel",
    OnAccept = function(_, data)
        if data and data.key and data.callback then
            data.apply(data.key)
            data.callback()
        end
    end,
    OnCancel = function(_, data)
        if data and data.callback then data.callback() end
    end,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
    preferredIndex = 3,
}

local function IsSecret(value)
    return issecretvalue and issecretvalue(value) == true
end

local function SafeCall(method, object, ...)
    if not method or IsSecret(object) or type(object) ~= "table" then return nil end
    local ok, result = pcall(method, object, ...)
    if ok then return result end
    return nil
end

local function BoolText(value)
    if value == nil then return "?" end
    return value and "yes" or "no"
end

local function NumberText(value, decimals)
    value = tonumber(value)
    if not value then return "?" end
    return ("%." .. tostring(decimals or 0) .. "f"):format(value)
end

local function GetObjectName(object)
    if type(object) ~= "table" or IsSecret(object) then return nil end
    local name = SafeCall(object.GetName, object)
    if name and name ~= "" then return name end
    local debugName = SafeCall(object.GetDebugName, object)
    if debugName and debugName ~= "" then return debugName end
    return nil
end

local function GetObjectLabel(object)
    if not object then return "none" end
    return GetObjectName(object) or SafeCall(object.GetObjectType, object) or tostring(object)
end

local function IsToolkitFrame(object)
    local current = object
    local depth = 0
    while current and type(current) == "table" and depth < 12 do
        local name = GetObjectName(current)
        if name and (name:find("^SinsToolkit") or name:find("^Sin's Toolkit")) then
            return true
        end
        current = SafeCall(current.GetParent, current)
        depth = depth + 1
    end
    return false
end

local function GetAddonFromSource(source)
    if type(source) ~= "string" then return nil end
    return source:match("^Interface\\AddOns\\([^\\]+)\\") or source:match("^Interface/AddOns/([^/]+)/")
end

local function GetSourceInfo(object)
    if type(object) ~= "table" or type(object.GetSourceLocation) ~= "function" then return nil end
    local ok, source, line = pcall(object.GetSourceLocation, object)
    if not ok or not source then return nil end
    return source, line, GetAddonFromSource(source)
end

local function GetMouseFrame()
    if GetMouseFocus then
        local ok, focus = pcall(GetMouseFocus)
        if ok and focus and not IsSecret(focus) then return focus end
    end
    if GetMouseFoci then
        local ok, foci = pcall(GetMouseFoci)
        if ok and type(foci) == "table" then
            for _, focus in ipairs(foci) do
                if focus and not IsSecret(focus) then return focus end
            end
        end
    end
    return nil
end

local function AddRow(rows, label, value)
    rows[#rows + 1] = { label = label, value = tostring(value == nil and "?" or value) }
end

local function BuildAnchorText(object)
    local count = SafeCall(object.GetNumPoints, object) or 0
    count = math.min(tonumber(count) or 0, 8)
    if count == 0 then return "none" end

    local lines = {}
    for i = 1, count do
        local ok, point, relativeTo, relativePoint, x, y = pcall(object.GetPoint, object, i)
        if ok and point then
            lines[#lines + 1] = ("%d. %s -> %s %s (%s, %s)"):format(
                i,
                point,
                GetObjectLabel(relativeTo),
                relativePoint or "?",
                NumberText(x),
                NumberText(y)
            )
        end
    end
    return #lines > 0 and table.concat(lines, "\n") or "none"
end

local function BuildParentChainText(object)
    local lines = {}
    local current = SafeCall(object.GetParent, object)
    local depth = 0
    while current and depth < 8 do
        depth = depth + 1
        lines[#lines + 1] = ("%d. %s"):format(depth, GetObjectLabel(current))
        current = SafeCall(current.GetParent, current)
    end
    return #lines > 0 and table.concat(lines, "\n") or "none"
end

local function BuildObjectPreview(object, methodName)
    local method = object and object[methodName]
    if type(method) ~= "function" then return "unavailable" end
    local ok, values = pcall(function()
        return { method(object) }
    end)
    if not ok then return "unavailable" end

    local count = #values
    local lines = {}
    for index = 1, math.min(count, 6) do
        local value = values[index]
        if value ~= nil then
            lines[#lines + 1] = ("%d. %s"):format(index, GetObjectLabel(value))
        end
    end

    if count == 0 then return "0" end
    if count > 6 then lines[#lines + 1] = ("...and %d more"):format(count - 6) end
    return ("%d\n%s"):format(count, table.concat(lines, "\n"))
end

local function BuildInspection(frame)
    local rows = {}
    if not frame then
        AddRow(rows, "Selection", "No frame selected.")
        return rows, "No frame selected.", ""
    end

    local name = GetObjectName(frame) or "(anonymous)"
    local objectType = SafeCall(frame.GetObjectType, frame) or "Unknown"
    local parent = SafeCall(frame.GetParent, frame)
    local source, line, addon = GetSourceInfo(frame)

    AddRow(rows, "Name", name)
    AddRow(rows, "Object type", objectType)
    AddRow(rows, "Parent", GetObjectLabel(parent))
    AddRow(rows, "Source", source and (source .. (line and (":" .. tostring(line)) or "")) or "?")
    AddRow(rows, "Inferred addon", addon or (source and "Blizzard or unknown" or "?"))
    AddRow(rows, "Width", NumberText(SafeCall(frame.GetWidth, frame)))
    AddRow(rows, "Height", NumberText(SafeCall(frame.GetHeight, frame)))
    AddRow(rows, "Scale", NumberText(SafeCall(frame.GetScale, frame), 2))
    AddRow(rows, "Effective scale", NumberText(SafeCall(frame.GetEffectiveScale, frame), 2))
    AddRow(rows, "Alpha", NumberText(SafeCall(frame.GetAlpha, frame), 2))
    AddRow(rows, "Frame strata", SafeCall(frame.GetFrameStrata, frame))
    AddRow(rows, "Frame level", SafeCall(frame.GetFrameLevel, frame))
    AddRow(rows, "Shown", BoolText(SafeCall(frame.IsShown, frame)))
    AddRow(rows, "Visible", BoolText(SafeCall(frame.IsVisible, frame)))
    AddRow(rows, "Mouse enabled", BoolText(SafeCall(frame.IsMouseEnabled, frame)))
    AddRow(rows, "Keyboard enabled", BoolText(SafeCall(frame.IsKeyboardEnabled, frame)))
    AddRow(rows, "Movable", BoolText(SafeCall(frame.IsMovable, frame)))
    AddRow(rows, "Resizable", BoolText(SafeCall(frame.IsResizable, frame)))
    AddRow(rows, "Protected", BoolText(SafeCall(frame.IsProtected, frame)))
    AddRow(rows, "Anchor points", BuildAnchorText(frame))
    AddRow(rows, "Parent chain", BuildParentChainText(frame))
    AddRow(rows, "Children", BuildObjectPreview(frame, "GetChildren"))
    AddRow(rows, "Regions", BuildObjectPreview(frame, "GetRegions"))

    local report = {}
    report[#report + 1] = "Sin's Toolkit Frame Inspector"
    report[#report + 1] = ("Captured: %s"):format(date and date("%Y-%m-%d %H:%M:%S") or "unknown")
    report[#report + 1] = ""
    for _, row in ipairs(rows) do
        report[#report + 1] = row.label .. ": " .. row.value
    end
    return rows, table.concat(report, "\n"), name
end

local function BuildHoverLines(frame)
    local rows = BuildInspection(frame)
    if not frame then
        return { "No frame under cursor.", "Move over a UI element to inspect it." }
    end

    local byLabel = {}
    for _, row in ipairs(rows or {}) do
        byLabel[row.label] = row.value
    end

    local lines = {}
    lines[#lines + 1] = "|cffd8a84aSin's Toolkit Hover Inspector|r"
    lines[#lines + 1] = "Frame: " .. (byLabel["Name"] or "(anonymous)")
    lines[#lines + 1] = "Type: " .. (byLabel["Object type"] or "?")
    lines[#lines + 1] = "Parent: " .. (byLabel["Parent"] or "?")
    if byLabel["Inferred addon"] then
        lines[#lines + 1] = "Addon: " .. byLabel["Inferred addon"]
    end
    lines[#lines + 1] = "Size: " .. (byLabel["Width"] or "?") .. " x " .. (byLabel["Height"] or "?")
    lines[#lines + 1] = "Shown/Visible: " .. (byLabel["Shown"] or "?") .. " / " .. (byLabel["Visible"] or "?")
    lines[#lines + 1] = "Protected: " .. (byLabel["Protected"] or "?")
    lines[#lines + 1] = "Copy Key: " .. GetCopyBindingText()
    lines[#lines + 1] = "|cff888888Use /stk to return. Copy Current opens a copy box.|r"
    return lines
end

local function EnsureHoverOverlay()
    if hoverOverlay then return end

    hoverOverlay = CreateFrame("Frame", addonName .. "FrameInspectorHoverOverlay", UIParent, "BackdropTemplate")
    hoverOverlay:SetFrameStrata("TOOLTIP")
    hoverOverlay:SetFrameLevel(920)
    hoverOverlay:SetSize(340, 150)
    hoverOverlay:EnableMouse(false)
    hoverOverlay:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 },
    })
    hoverOverlay:SetBackdropColor(0.02, 0.018, 0.026, 0.94)
    hoverOverlay:SetBackdropBorderColor(0.72, 0.40, 0.93, 0.85)

    hoverText = hoverOverlay:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    hoverText:SetPoint("TOPLEFT", hoverOverlay, "TOPLEFT", 10, -10)
    hoverText:SetPoint("BOTTOMRIGHT", hoverOverlay, "BOTTOMRIGHT", -10, 10)
    hoverText:SetJustifyH("LEFT")
    hoverText:SetJustifyV("TOP")
    hoverOverlay:Hide()
end

local function PositionHoverOverlay()
    if not hoverOverlay or not GetCursorPosition then return end
    local scale = UIParent and UIParent:GetEffectiveScale() or 1
    local x, y = GetCursorPosition()
    x = (x or 0) / scale
    y = (y or 0) / scale

    hoverOverlay:ClearAllPoints()
    hoverOverlay:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", x + 18, y + 18)
end

local function UpdateHover()
    EnsureHoverOverlay()

    local focus = GetMouseFrame()
    if IsToolkitFrame(focus) then focus = nil end
    selection = focus

    local rows
    rows, lastReport, lastName = BuildInspection(selection)
    hoverLastFrame = selection
    hoverText:SetText(table.concat(BuildHoverLines(selection), "\n"))
    PositionHoverOverlay()
    hoverOverlay:Show()
    return rows, lastReport, lastName
end

local function EnsureHoverFrame()
    if hoverFrame then return end
    hoverFrame = CreateFrame("Frame", addonName .. "FrameInspectorHoverDriver")
    hoverFrame:SetScript("OnUpdate", function(_, elapsed)
        if not hoverActive then return end
        hoverElapsed = hoverElapsed + elapsed
        if hoverElapsed < 0.08 then
            PositionHoverOverlay()
            return
        end
        hoverElapsed = 0
        UpdateHover()
    end)
end

function Inspector:InspectCursor()
    if frozen and selection then return self:Refresh() end
    return self:InspectFrame(GetMouseFrame())
end

function Inspector:InspectFrame(frame)
    if frame and IsSecret(frame) then frame = nil end
    selection = frame
    return self:Refresh()
end

function Inspector:GetSelection()
    return selection
end

function Inspector:SetFrozen(value)
    frozen = value == true
    return frozen
end

function Inspector:IsFrozen()
    return frozen
end

function Inspector:Refresh()
    local rows
    rows, lastReport, lastName = BuildInspection(selection)
    if selection and highlightActive then
        self:HighlightSelection()
    elseif not selection then
        self:ClearHighlight()
    end
    return rows, lastReport, lastName
end

function Inspector:BuildReport()
    if lastReport == "" then self:Refresh() end
    return lastReport
end

local function ShowCopyPopup(title, text, options)
    local data = text or ""
    if options and options.stopHoverAfterClose then
        data = {
            text = text or "",
            stopHoverAfterClose = true,
        }
    end
    if StaticPopup_Show then
        StaticPopup_Show("SinsToolkitFrameInspectorCopy", title, nil, data)
    else
        Print(text or "")
        if options and options.stopHoverAfterClose then
            Inspector:StopHoverInspector(true)
        end
    end
end

local function GetBindingActionName(command)
    if not command or command == "" then return "Unknown" end
    if GetBindingText then
        local text = GetBindingText(command, "BINDING_NAME_")
        if text and text ~= "" then return text end
    end
    return _G["BINDING_NAME_" .. command] or command
end

local function GetCopyBindingKey()
    if not GetBindingKey then return nil end
    local key = GetBindingKey("SINSTOOLKIT_COPY_FRAME_INSPECTOR")
    return key
end

function GetCopyBindingText()
    return GetCopyBindingKey() or "Not Assigned"
end

local function SaveCurrentBindings()
    if SaveBindings and GetCurrentBindingSet then
        SaveBindings(GetCurrentBindingSet())
    end
end

local function ApplyCopyBinding(key)
    if not key or key == "" or not SetBinding then return false end
    local oldKey1, oldKey2 = GetBindingKey and GetBindingKey("SINSTOOLKIT_COPY_FRAME_INSPECTOR")
    if oldKey1 then SetBinding(oldKey1, nil) end
    if oldKey2 then SetBinding(oldKey2, nil) end
    SetBinding(key, "SINSTOOLKIT_COPY_FRAME_INSPECTOR")
    SaveCurrentBindings()
    return true
end

local function BuildKeyName(key)
    if not key or key == "" then return nil end
    if key == "LSHIFT" or key == "RSHIFT" or key == "LCTRL" or key == "RCTRL" or key == "LALT" or key == "RALT" then
        return nil
    end
    if key == "UNKNOWN" then return nil end

    local parts = {}
    if IsControlKeyDown and IsControlKeyDown() and key ~= "LCTRL" and key ~= "RCTRL" then parts[#parts + 1] = "CTRL" end
    if IsShiftKeyDown and IsShiftKeyDown() and key ~= "LSHIFT" and key ~= "RSHIFT" then parts[#parts + 1] = "SHIFT" end
    if IsAltKeyDown and IsAltKeyDown() and key ~= "LALT" and key ~= "RALT" then parts[#parts + 1] = "ALT" end
    parts[#parts + 1] = key
    return table.concat(parts, "-")
end

local function StopKeyCapture()
    if keyCaptureFrame then
        keyCaptureFrame:Hide()
        keyCaptureFrame:EnableKeyboard(false)
        keyCaptureFrame:SetScript("OnKeyDown", nil)
    end
    keyCaptureCallback = nil
end

local function EnsureKeyCaptureFrame()
    if keyCaptureFrame then return end

    keyCaptureFrame = CreateFrame("Frame", addonName .. "FrameInspectorKeyCapture", UIParent, "BackdropTemplate")
    keyCaptureFrame:SetFrameStrata("FULLSCREEN_DIALOG")
    keyCaptureFrame:SetFrameLevel(1100)
    keyCaptureFrame:SetSize(360, 116)
    keyCaptureFrame:SetPoint("CENTER")
    keyCaptureFrame:EnableMouse(true)
    keyCaptureFrame:Hide()
    keyCaptureFrame:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 },
    })
    keyCaptureFrame:SetBackdropColor(0.02, 0.018, 0.026, 0.96)
    keyCaptureFrame:SetBackdropBorderColor(0.72, 0.40, 0.93, 0.85)

    keyCaptureText = keyCaptureFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    keyCaptureText:SetPoint("CENTER")
    keyCaptureText:SetWidth(320)
    keyCaptureText:SetJustifyH("CENTER")
    keyCaptureText:SetText("Press a key to assign.\nPress Escape to cancel.")
end

function Inspector:CopyFrameName()
    if lastName == "" then self:Refresh() end
    ShowCopyPopup("Frame Name", lastName ~= "" and lastName or "No frame selected.")
end

function Inspector:CopyReport()
    ShowCopyPopup("Frame Inspector Report", self:BuildReport())
end

function Inspector:GetCopyKeyText()
    return GetCopyBindingText()
end

function Inspector:ClearCopyKey()
    if not SetBinding then return false end
    local key1, key2 = GetBindingKey and GetBindingKey("SINSTOOLKIT_COPY_FRAME_INSPECTOR")
    if key1 then SetBinding(key1, nil) end
    if key2 then SetBinding(key2, nil) end
    SaveCurrentBindings()
    if keyCaptureCallback then keyCaptureCallback() end
    return true
end

function Inspector:SetCopyKey(key, callback)
    if not key or not SetBinding then return false end

    local existing = GetBindingAction and GetBindingAction(key) or nil
    if existing and existing ~= "" and existing ~= "SINSTOOLKIT_COPY_FRAME_INSPECTOR" then
        if StaticPopup_Show then
            StaticPopup_Show("SinsToolkitFrameInspectorReplaceBinding", key, GetBindingActionName(existing), {
                key = key,
                callback = callback,
                apply = ApplyCopyBinding,
            })
        end
        return false
    end

    ApplyCopyBinding(key)
    if callback then callback() end
    return true
end

function Inspector:BeginCopyKeyCapture(callback)
    EnsureKeyCaptureFrame()
    keyCaptureCallback = callback
    keyCaptureText:SetText("Press a key to assign.\nPress Escape to cancel.")
    keyCaptureFrame:SetScript("OnKeyDown", function(_, key)
        if key == "ESCAPE" then
            StopKeyCapture()
            if callback then callback() end
            return
        end

        local bindingKey = BuildKeyName(key)
        if not bindingKey then return end
        keyCaptureText:SetText("Selected: " .. bindingKey)
        StopKeyCapture()
        self:SetCopyKey(bindingKey, callback)
    end)
    keyCaptureFrame:EnableKeyboard(true)
    keyCaptureFrame:Show()
    return true
end

function Inspector:StartHoverInspector()
    EnsureHoverFrame()
    EnsureHoverOverlay()
    hoverActive = true
    frozen = false
    hoverElapsed = 1

    local mainFrame = SinsToolkit.Window and SinsToolkit.Window.mainFrame
    if mainFrame and mainFrame:IsShown() then
        mainFrame:Hide()
    end

    UpdateHover()
    Print("Hover Inspector started. Move over any UI frame, then type /stk to return.")
    return true
end

function Inspector:StopHoverInspector(restoreWindow)
    hoverActive = false
    hoverLastFrame = nil
    if hoverOverlay then hoverOverlay:Hide() end
    if restoreWindow and SinsToolkit.Window and SinsToolkit.Window.Show then
        SinsToolkit.Window:Show()
    end
    return true
end

function Inspector:IsHoverInspecting()
    return hoverActive
end

function Inspector:GetHoverSelection()
    return selection or hoverLastFrame
end

function Inspector:CopyCurrent()
    if hoverActive then
        UpdateHover()
    elseif lastReport == "" then
        self:Refresh()
    end
    ShowCopyPopup("Frame Inspector Current", lastReport ~= "" and lastReport or "No frame selected.")
end

function Inspector:CopyCurrentFromBinding()
    if hoverActive then
        UpdateHover()
    elseif lastReport == "" then
        self:Refresh()
    end
    ShowCopyPopup("Frame Inspector Current", lastReport ~= "" and lastReport or "No frame selected.", {
        stopHoverAfterClose = true,
    })
end

function SinsToolkit_CopyFrameInspectorCurrent()
    local inspector = SinsToolkit.DevTools and SinsToolkit.DevTools.FrameInspector
    if inspector and inspector.CopyCurrentFromBinding then
        inspector:CopyCurrentFromBinding()
    end
end

function Inspector:HighlightSelection()
    if not selection then return false end
    highlightActive = true
    if not highlightFrame then
        highlightFrame = CreateFrame("Frame", addonName .. "FrameInspectorHighlight", UIParent, "BackdropTemplate")
        highlightFrame:SetFrameStrata("TOOLTIP")
        highlightFrame:SetFrameLevel(900)
        highlightFrame:EnableMouse(false)
        highlightFrame:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            edgeSize = 2,
            insets = { left = 0, right = 0, top = 0, bottom = 0 },
        })
        highlightFrame:SetBackdropColor(0.72, 0.40, 0.93, 0.14)
        highlightFrame:SetBackdropBorderColor(0.95, 0.80, 0.22, 0.95)
        highlightFrame:Hide()
    end
    local ok = pcall(function()
        highlightFrame:ClearAllPoints()
        highlightFrame:SetAllPoints(selection)
        highlightFrame:Show()
    end)
    if not ok then
        highlightFrame:Hide()
        return false
    end
    return true
end

function Inspector:ClearHighlight()
    highlightActive = false
    if highlightFrame then highlightFrame:Hide() end
end

function Inspector:OpenFrameStack()
    if UIParentLoadAddOn then pcall(UIParentLoadAddOn, "Blizzard_DebugTools") end
    if FrameStackTooltip and FrameStackTooltip.IsVisible and FrameStackTooltip:IsVisible() then
        return true
    end
    if FrameStackTooltip_Show then
        local showHidden = FrameStackTooltip_IsShowHiddenEnabled and FrameStackTooltip_IsShowHiddenEnabled() or false
        local showRegions = FrameStackTooltip_IsShowRegionsEnabled and FrameStackTooltip_IsShowRegionsEnabled() or false
        local showAnchors = FrameStackTooltip_IsShowAnchorsEnabled and FrameStackTooltip_IsShowAnchorsEnabled() or false
        pcall(FrameStackTooltip_Show, FrameStackTooltip, showHidden, showRegions, showAnchors)
        Print("Opened Blizzard /fstack. It is a separate Blizzard overlay and may stay open independently.")
        return true
    end
    if FrameStackTooltip_Toggle then
        pcall(FrameStackTooltip_Toggle, true, true, true)
        Print("Opened Blizzard /fstack. It is a separate Blizzard overlay and may stay open independently.")
        return true
    end
    Print("Use /fstack to open Blizzard's separate Frame Stack overlay.")
    return false
end

function Inspector:OpenTableInspector()
    if UIParentLoadAddOn then pcall(UIParentLoadAddOn, "Blizzard_DebugTools") end
    if selection and DisplayTableInspectorWindow then
        pcall(DisplayTableInspectorWindow, selection, "Sin's Toolkit Frame Inspector")
        return true
    end
    if selection and TableAttributeDisplay and TableAttributeDisplay.InspectTable then
        pcall(function()
            TableAttributeDisplay:InspectTable(selection)
            TableAttributeDisplay:Show()
        end)
        return true
    end
    Print("Blizzard Table Inspector is unavailable. Open /fstack and Ctrl-click a highlighted frame if your client supports it.")
    return false
end
