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 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
        editBox:SetMaxLetters(0)
        editBox:SetText(data or "")
        editBox:HighlightText()
        editBox:SetScript("OnEscapePressed", function(box) box:GetParent():Hide() end)
        editBox:SetScript("OnEnterPressed", function(box) box:GetParent():Hide() end)
    end,
    hasEditBox = true,
    editBoxWidth = 520,
    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 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

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)
    if StaticPopup_Show then
        StaticPopup_Show("SinsToolkitFrameInspectorCopy", title, nil, text or "")
    else
        Print(text or "")
    end
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: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
